ensymble-0.29/0000775000076400007640000000000011374344735012345 5ustar stevesteveensymble-0.29/ensymble.py0000775000076400007640000000577611373531772014555 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # ensymble.py - Ensymble command line tool # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import sys import os # needed for modules under ensymble.action to correctly find ensymble.utils.* import ensymble sys.path.append(os.path.dirname(ensymble.__file__)) # Import command modules from actions from ensymble.actions import * def main(): pgmname = os.path.basename(sys.argv[0]) debug = False # Parse command line parameters. try: if len(sys.argv) < 2 or sys.argv[1] in ("-h", "--help"): # No command given, print help. commands = [] for cmd in cmddict.keys(): commands.append(" %-12s %s" % (cmd, cmddict[cmd].shorthelp)) commands.sort() commands = "\n".join(commands) print ( ''' Ensymble developer utilities for Symbian OS usage: %(pgmname)s command [command options]... Commands: %(commands)s Use '%(pgmname)s command --help' to get command specific help. ''' % locals()) return 0 command = sys.argv[1] if command not in cmddict.keys(): raise ValueError("invalid command '%s'" % command) if "-h" in sys.argv[2:] or "--help" in sys.argv[2:]: # Print command specific help. longhelp = cmddict[command].longhelp print ( ''' Ensymble developer utilities for Symbian OS usage: %(pgmname)s %(longhelp)s''' % locals()) else: if "--debug" in sys.argv[2:]: # Enable raw exception reporting. debug = True # Run command. cmddict[command].run(pgmname, sys.argv[2:]) except Exception, e: if debug: # Debug output requested, print exception traceback as-is. raise else: # Normal output, use terse exception reporting. return "%s: %s" % (pgmname, str(e)) return 0 # Call main if run as stand-alone executable. if __name__ == '__main__': sys.exit(main()) # Call main regardless, to support packing with the squeeze utility. # sys.exit(main()) ensymble-0.29/ensymble/0000775000076400007640000000000011374344735014163 5ustar stevesteveensymble-0.29/ensymble/actions/0000775000076400007640000000000011374344735015623 5ustar stevesteveensymble-0.29/ensymble/actions/__init__.py0000664000076400007640000000276211374343415017735 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## """ actions - Commands used by the ensymble tool """ __all__ = ["altere32", "genuid", "infoe32", "mergesis", "py2sis", "signsis", "simplesis"] cmddict = {} for _ in __all__: cmddict[_] = __import__(_, globals(), locals(), []) from .. import __version__ class Version: shorthelp = "Print Ensymble version" longhelp = """version Print Ensymble version""" def run(self, pgmname, argv): print __version__ cmddict['version'] = Version() __all__.append('cmddict') ensymble-0.29/ensymble/actions/genuid.py0000664000076400007640000001063711373531772017455 0ustar stevesteve#!/usr/bin/env python # -*- coding: iso8859-1 -*- ############################################################################## # cmd_genuid.py - Ensymble command line tool, genuid command # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import sys import os import getopt import locale import struct from utils import symbianutil ############################################################################## # Help texts ############################################################################## shorthelp = 'Generate a new test-range UID from a name' longhelp = '''genuid [--encoding=terminal,filesystem] [--verbose] ... Generate a new test-range UID from a name. Options: name - Name used for UID generation encoding - Local character encodings for terminal and filesystem verbose - Not used Generated UID is compatible with the automatic UID generation of py2sis and simplesis commands. The name must not contain version information or any file prefixes, just the name itself, e.g. "mymodule" instead of "mymodule_v1.2.3.sis". ''' ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python [mergefile]... Merge several SIS packages into one and sign the resulting SIS file with the certificate provided. The first SIS file is used as the base file and the remaining SIS files are added as unconditional embedded SIS files into it. Any signatures present in the first SIS file are stripped. Options: infile - Path of the base SIS file mergefile - Path of SIS file(s) to add to the base SIS file outfile - Path of the resulting SIS file cert - Certificate to use for signing (PEM format) privkey - Private key of the certificate (PEM format) passphrase - Pass phrase of the private key (insecure, use stdin instead) encoding - Local character encodings for terminal and filesystem verbose - Print extra statistics Merging SIS files that already contain other SIS files is not supported. ''' ############################################################################## # Parameters ############################################################################## MAXPASSPHRASELENGTH = 256 MAXCERTIFICATELENGTH = 65536 MAXPRIVATEKEYLENGTH = 65536 MAXSISFILESIZE = 1024 * 1024 * 8 # Eight megabytes ############################################################################## # Global variables ############################################################################## debug = False ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python MAXCERTIFICATELENGTH: raise ValueError("certificate file too large") # Read private key file. f = file(privkey, "rb") privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1) f.close() if len(privkeydata) > MAXPRIVATEKEYLENGTH: raise ValueError("private key file too large") elif cert == None and privkey == None: # No certificate given, use the Ensymble default certificate. # defaultcert.py is not imported when not needed. This speeds # up program start-up a little. from utils import defaultcert certdata = defaultcert.cert privkeydata = defaultcert.privkey print ("%s: warning: no certificate given, using " "insecure built-in one" % pgmname) else: raise ValueError("missing certificate or private key") # Get pass phrase. Pass phrase remains in terminal encoding. passphrase = opts.get("--passphrase", opts.get("-p", None)) if passphrase == None and privkey != None: # Private key given without "--passphrase" option, ask it. if sys.stdin.isatty(): # Standard input is a TTY, ask password interactively. passphrase = getpass.getpass("Enter private key pass phrase:") else: # Not connected to a TTY, read stdin non-interactively instead. passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1) if len(passphrase) > MAXPASSPHRASELENGTH: raise ValueError("pass phrase too long") passphrase = passphrase.strip() # Determine verbosity. verbose = False if "--verbose" in opts.keys() or "-v" in opts.keys(): verbose = True # Determine if debug output is requested. if "--debug" in opts.keys(): debug = True # Enable debug output for OpenSSL-related functions. cryptutil.setdebug(True) # Ingredients for successful SIS generation: # # terminalenc Terminal character encoding (autodetected) # filesystemenc File system name encoding (autodetected) # infiles A list of input SIS file names, filesystemenc encoded # outfile Output SIS file name, filesystemenc encoded # cert Certificate in PEM format # privkey Certificate private key in PEM format # passphrase Pass phrase of priv. key, terminalenc encoded string # verbose Boolean indicating verbose terminal output if verbose: print print "Input SIS files %s" % " ".join( [f.decode(filesystemenc).encode(terminalenc) for f in infiles]) print "Output SIS file %s" % ( outfile.decode(filesystemenc).encode(terminalenc)) print "Certificate %s" % ((cert and cert.decode(filesystemenc).encode(terminalenc)) or "") print "Private key %s" % ((privkey and privkey.decode(filesystemenc).encode(terminalenc)) or "") print insis = [] for n in xrange(len(infiles)): # Read input SIS files. f = file(infiles[n], "rb") instring = f.read(MAXSISFILESIZE + 1) f.close() if len(instring) > MAXSISFILESIZE: raise ValueError("%s: input SIS file too large" % infiles[n]) if n == 0: # Store UIDs for later use. uids = instring[:16] # UID1, UID2, UID3 and UIDCRC # Convert input SIS file to SISFields. sf, rlen = sisfield.SISField(instring[16:], False) # Ignore extra bytes after SIS file. if len(instring) > (rlen + 16): print ("%s: %s: warning: %d extra bytes after SIS file (ignored)" % (pgmname, infiles[n], (len(instring) - (rlen + 16)))) # Try to release some memory early. del instring # Check that there are no embedded SIS files. if len(sf.Data.DataUnits) > 1: raise ValueError("%s: input SIS file contains " "embedded SIS files" % infiles[n]) insis.append(sf) # Temporarily remove the SISDataIndex SISField from the first SISController. ctrlfield = insis[0].Controller.Data didxfield = ctrlfield.DataIndex ctrlfield.DataIndex = None # Remove old signatures from the first SIS file. if len(ctrlfield.getsignatures()) > 0: print ("%s: warning: removing old signatures " "from the first input SIS file" % pgmname) ctrlfield.setsignatures([]) for n in xrange(1, len(insis)): # Append SISDataUnit SISFields into SISData array of the first SIS file. insis[0].Data.DataUnits.append(insis[n].Data.DataUnits[0]) # Set data index in SISController SISField. insis[n].Controller.Data.DataIndex.DataIndex = n # Embed SISController into SISInstallBlock of the first SIS file. ctrlfield.InstallBlock.EmbeddedSISFiles.append(insis[n].Controller.Data) # Calculate a signature of the modified SISController. string = ctrlfield.tostring() string = sisfield.stripheaderandpadding(string) signature, algoid = sisfile.signstring(privkeydata, passphrase, string) # Create a SISCertificateChain SISField from certificate data. sf1 = sisfield.SISBlob(Data = cryptutil.certtobinary(certdata)) sf2 = sisfield.SISCertificateChain(CertificateData = sf1) # Create a SISSignature SISField from calculated signature. sf3 = sisfield.SISString(String = algoid) sf4 = sisfield.SISSignatureAlgorithm(AlgorithmIdentifier = sf3) sf5 = sisfield.SISBlob(Data = signature) sf6 = sisfield.SISSignature(SignatureAlgorithm = sf4, SignatureData = sf5) # Create a new SISSignatureCertificateChain SISField. sa = sisfield.SISArray(SISFields = [sf6]) sf7 = sisfield.SISSignatureCertificateChain(Signatures = sa, CertificateChain = sf2) # Set certificate, restore data index. ctrlfield.Signature0 = sf7 ctrlfield.DataIndex = didxfield # Convert SISFields to string. outstring = insis[0].tostring() # Write output SIS file. f = file(outfile, "wb") f.write(uids) f.write(outstring) f.close() ensymble-0.29/ensymble/actions/py2sis.py0000664000076400007640000012166111373531772017433 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # cmd_py2sis.py - Ensymble command line tool, py2sis command # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import sys import os import re import getopt import getpass import locale import zlib from utils import sisfile from utils import sisfield from utils import symbianutil from utils import rscfile from utils import miffile ############################################################################## # Help texts ############################################################################## shorthelp = 'Create a SIS package for a "Python for S60" application' longhelp = '''py2sis [--uid=0x01234567] [--appname=AppName] [--version=1.0.0] [--lang=EN,...] [--icon=icon.svg] [--shortcaption="App. Name",...] [--caption="Application Name",...] [--drive=C] [extrasdir=root] [--textfile=mytext_%C.txt] [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] [--heapsize=min,max] [--caps=Cap1+Cap2+...] [--vendor="Vendor Name",...] [--autostart] [--runinstall] [--encoding=terminal,filesystem] [--verbose] [sisfile] Create a SIS package for a "Python for S60" application. Options: src - Source script or directory sisfile - Path of the created SIS file uid - Symbian OS UID for the application appname - Name of the application version - Application version: X.Y.Z or X,Y,Z (major, minor, build) lang - Comma separated list of two-character language codes icon - Icon file in SVG-Tiny format shortcaption - Comma separated list of short captions in all languages caption - Comma separated list of long captions in all languages drive - Drive where the package will be installed (any by default) extrasdir - Name of dir. tree placed under drive root (none by default) textfile - Text file (or pattern, see below) to display during install cert - Certificate to use for signing (PEM format) privkey - Private key of the certificate (PEM format) passphrase - Pass phrase of the private key (insecure, use stdin instead) caps - Capability names, separated by "+" (none by default) vendor - Vendor name or a comma separated list of names in all lang. autostart - Application is registered to start on each device boot runinstall - Application is automatically started after installation heapsize - Application heap size, min. and/or max. ("4k,1M" by default) encoding - Local character encodings for terminal and filesystem verbose - Print extra statistics If no certificate and its private key are given, a default self-signed certificate is used to sign the SIS file. Software authors are encouraged to create their own unique certificates for SIS packages that are to be distributed. If no icon is given, the Python logo is used as the icon. The Python logo is a trademark of the Python Software Foundation. Text to display uses UTF-8 encoding. The file name may contain formatting characters that are substituted for each selected language. If no formatting characters are present, the same text will be used for all languages. %% - literal % %n - language number (01 - 99) %c - two-character language code in lowercase letters %C - two-character language code in capital letters %l - language name in English, using only lowercase letters %l - language name in English, using mixed case letters ''' ############################################################################## # Parameters ############################################################################## MAXPASSPHRASELENGTH = 256 MAXCERTIFICATELENGTH = 65536 MAXPRIVATEKEYLENGTH = 65536 MAXICONFILESIZE = 65536 MAXOTHERFILESIZE = 1024 * 1024 * 8 # Eight megabytes MAXTEXTFILELENGTH = 1024 ############################################################################## # Global variables ############################################################################## debug = False ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python MAXICONFILESIZE: raise ValueError("icon file too large") else: # No icon given, use a default icon. icondata = zlib.decompress(defaulticondata.decode("base-64")) # Determine application short caption(s). shortcaption = opts.get("--shortcaption", opts.get("-s", "")) shortcaption = shortcaption.decode(terminalenc) if len(shortcaption) == 0: # Short caption not given, use application name. shortcaption = [appname] * numlang else: shortcaption = shortcaption.split(",") # Determine application long caption(s), use short caption by default. caption = opts.get("--caption", opts.get("-c", "")) caption = caption.decode(terminalenc) if len(caption) == 0: # Caption not given, use short caption. caption = shortcaption else: caption = caption.split(",") # Compare the number of languages and captions. if len(shortcaption) != numlang or len(caption) != numlang: raise ValueError("invalid number of captions") # Determine installation drive, any by default. drive = opts.get("--drive", opts.get("-f", "any")).upper() if drive == "ANY" or drive == "!": drive = "!" elif drive != "C" and drive != "E": raise ValueError("%s: invalid drive letter" % drive) # Determine vendor name(s), use "Ensymble" by default. vendor = opts.get("--vendor", opts.get("-d", "Ensymble")) vendor = vendor.decode(terminalenc) vendor = vendor.split(",") if len(vendor) == 1: # Only one vendor name given, use it for all languages. vendor = vendor * numlang elif len(vendor) != numlang: raise ValueError("invalid number of vendor names") extrasdir = opts.get("--extrasdir", opts.get("-x", None)) if extrasdir != None: extrasdir = extrasdir.decode(terminalenc).encode(filesystemenc) if extrasdir[-1] == os.sep: # Strip trailing slash (or backslash). extrasdir = extrasdir[:-1] if os.sep in extrasdir: raise ValueError("%s: too many path components" % extrasdir) # Load text files. texts = [] textfile = opts.get("--textfile", opts.get("-t", None)) if textfile != None: texts = readtextfiles(textfile, lang) # Get certificate and its private key file names. cert = opts.get("--cert", opts.get("-a", None)) privkey = opts.get("--privkey", opts.get("-k", None)) if cert != None and privkey != None: # Convert file names from terminal encoding to filesystem encoding. cert = cert.decode(terminalenc).encode(filesystemenc) privkey = privkey.decode(terminalenc).encode(filesystemenc) # Read certificate file. f = file(cert, "rb") certdata = f.read(MAXCERTIFICATELENGTH + 1) f.close() if len(certdata) > MAXCERTIFICATELENGTH: raise ValueError("certificate file too large") # Read private key file. f = file(privkey, "rb") privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1) f.close() if len(privkeydata) > MAXPRIVATEKEYLENGTH: raise ValueError("private key file too large") elif cert == None and privkey == None: # No certificate given, use the Ensymble default certificate. # defaultcert.py is not imported when not needed. This speeds # up program start-up a little. from utils import defaultcert certdata = defaultcert.cert privkeydata = defaultcert.privkey print ("%s: warning: no certificate given, using " "insecure built-in one" % pgmname) # Warn if the UID is in the protected range. # Resulting SIS file will probably not install. if uid3 < 0x80000000L: print ("%s: warning: UID is in the protected range " "(0x00000000 - 0x7ffffff)" % pgmname) else: raise ValueError("missing certificate or private key") # Get pass phrase. Pass phrase remains in terminal encoding. passphrase = opts.get("--passphrase", opts.get("-p", None)) if passphrase == None and privkey != None: # Private key given without "--passphrase" option, ask it. if sys.stdin.isatty(): # Standard input is a TTY, ask password interactively. passphrase = getpass.getpass("Enter private key pass phrase:") else: # Not connected to a TTY, read stdin non-interactively instead. passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1) if len(passphrase) > MAXPASSPHRASELENGTH: raise ValueError("pass phrase too long") passphrase = passphrase.strip() # Get capabilities and normalize the names. caps = opts.get("--caps", opts.get("-b", "")) capmask = symbianutil.capstringtomask(caps) caps = symbianutil.capmasktostring(capmask, True) # Determine if the application is requested to start on each device boot. autostart = False if "--autostart" in opts.keys() or "-g" in opts.keys(): autostart = True runinstall = False if "--runinstall" in opts.keys() or "-R" in opts.keys(): runinstall = True # Get heap sizes. heapsize = opts.get("--heapsize", opts.get("-H", "4k,1M")).split(",", 1) try: heapsizemin = symbianutil.parseintmagnitude(heapsize[0]) if len(heapsize) == 1: # Only one size given, use it as both. heapsizemax = heapsizemin else: heapsizemax = symbianutil.parseintmagnitude(heapsize[1]) except (ValueError, TypeError, IndexError): raise ValueError("%s: invalid heap size, one or two values expected" % ",".join(heapsize)) # Warn if the minimum heap size is larger than the maximum heap size. # Resulting SIS file will probably not install. if heapsizemin > heapsizemax: print ("%s: warning: minimum heap size larger than " "maximum heap size" % pgmname) # Determine verbosity. verbose = False if "--verbose" in opts.keys() or "-v" in opts.keys(): verbose = True # Determine if debug output is requested. if "--debug" in opts.keys(): debug = True # Enable debug output for OpenSSL-related functions. import cryptutil cryptutil.setdebug(True) # Ingredients for successful SIS generation: # # terminalenc Terminal character encoding (autodetected) # filesystemenc File system name encoding (autodetected) # basename Base for generated file names on host, filesystemenc encoded # srcdir Directory of source files, filesystemenc encoded # srcfiles List of filesystemenc encoded source file names in srcdir # outfile Output SIS file name, filesystemenc encoded # uid3 Application UID3, long integer # appname Application name and install directory in device, in Unicode # version A triple-item tuple (major, minor, build) # lang List of two-character language codes, ASCII strings # icon Icon data, a binary string typically containing a SVG-T file # shortcaption List of Unicode short captions, one per language # caption List of Unicode long captions, one per language # drive Installation drive letter or "!" # extrasdir Path prefix for extra files, filesystemenc encoded or None # textfile File name pattern of text file(s) to display during install # texts Actual texts to display during install, one per language # cert Certificate in PEM format # privkey Certificate private key in PEM format # passphrase Pass phrase of private key, terminalenc encoded string # caps, capmask Capability names and bitmask # vendor List of Unicode vendor names, one per language # autostart Boolean requesting application autostart on device boot # runinstall Boolean requesting application autorun after installation # heapsizemin Heap that must be available for the application to start # heapsizemax Maximum amount of heap the application can allocate # verbose Boolean indicating verbose terminal output if verbose: print print "Input file(s) %s" % " ".join( [s.decode(filesystemenc).encode(terminalenc) for s in srcfiles]) print "Output SIS file %s" % ( outfile.decode(filesystemenc).encode(terminalenc)) print "UID 0x%08x" % uid3 print "Application name %s" % appname.encode(terminalenc) print "Version %d.%d.%d" % ( version[0], version[1], version[2]) print "Language(s) %s" % ", ".join(lang) print "Icon %s" % ((icon and icon.decode(filesystemenc).encode(terminalenc)) or "") print "Short caption(s) %s" % ", ".join( [s.encode(terminalenc) for s in shortcaption]) print "Long caption(s) %s" % ", ".join( [s.encode(terminalenc) for s in caption]) print "Install drive %s" % ((drive == "!") and "" or drive) print "Extras directory %s" % ((extrasdir and extrasdir.decode(filesystemenc).encode(terminalenc)) or "") print "Text file(s) %s" % ((textfile and textfile.decode(filesystemenc).encode(terminalenc)) or "") print "Certificate %s" % ((cert and cert.decode(filesystemenc).encode(terminalenc)) or "") print "Private key %s" % ((privkey and privkey.decode(filesystemenc).encode(terminalenc)) or "") print "Capabilities 0x%x (%s)" % (capmask, caps) print "Vendor name(s) %s" % ", ".join( [s.encode(terminalenc) for s in vendor]) print "Autostart on boot %s" % ((autostart and "Yes") or "No") print "Run after install %s" % ((runinstall and "Yes") or "No") print "Heap size in bytes %d, %d" % (heapsizemin, heapsizemax) print # Generate SimpleSISWriter object. sw = sisfile.SimpleSISWriter(lang, caption, uid3, version, vendor[0], vendor) # Add text file or files to the SIS object. Text dialog is # supposed to be displayed before anything else is installed. if len(texts) == 1: sw.addfile(texts[0], operation = sisfield.EOpText) elif len(texts) > 1: sw.addlangdepfile(texts, operation = sisfield.EOpText) # Generate "Python for S60" resource file. rsctarget = u"%s:\\resource\\apps\\%s_0x%08x.rsc" % (drive, appname, uid3) string = zlib.decompress(pythons60rscdata.decode("base-64")) sw.addfile(string, rsctarget) del string # Generate application registration resource file. regtarget = u"%s:\\private\\10003a3f\\import\\apps\\%s_0x%08x_reg.rsc" % ( drive, appname, uid3) exename = u"%s_0x%08x" % (appname, uid3) locpath = u"\\resource\\apps\\%s_0x%08x_loc" % (appname, uid3) rw = rscfile.RSCWriter(uid2 = 0x101f8021, uid3 = uid3) # STRUCT APP_REGISTRATION_INFO from appinfo.rh res = rscfile.Resource(["LONG", "LLINK", "LTEXT", "LONG", "LTEXT", "LONG", "BYTE", "BYTE", "BYTE", "BYTE", "LTEXT", "BYTE", "WORD", "WORD", "WORD", "LLINK"], 0, 0, exename, 0, locpath, 1, 0, 0, 0, 0, "", 0, 0, 0, 0, 0) rw.addresource(res) string = rw.tostring() del rw sw.addfile(string, regtarget) del string # EXE target name exetarget = u"%s:\\sys\\bin\\%s_0x%08x.exe" % (drive, appname, uid3) # Generate autostart registration resource file, if requested. if autostart: autotarget = u"%s:\\private\\101f875a\\import\\[%08x].rsc" % ( drive, uid3) rw = rscfile.RSCWriter(uid2 = 0, offset = " ") # STRUCT STARTUP_ITEM_INFO from startupitem.rh res = rscfile.Resource(["BYTE", "LTEXT", "WORD", "LONG", "BYTE", "BYTE"], 0, exetarget, 0, 0, 0, 0) rw.addresource(res) string = rw.tostring() del rw sw.addfile(string, autotarget) del string # Generate localisable icon/caption definition resource files. iconpath = "\\resource\\apps\\%s_0x%08x_aif.mif" % (appname, uid3) for n in xrange(numlang): loctarget = u"%s:\\resource\\apps\\%s_0x%08x_loc.r%02d" % ( drive, appname, uid3, symbianutil.langidtonum[lang[n]]) rw = rscfile.RSCWriter(uid2 = 0, offset = " ") # STRUCT LOCALISABLE_APP_INFO from appinfo.rh res = rscfile.Resource(["LONG", "LLINK", "LTEXT", "LONG", "LLINK", "LTEXT", "WORD", "LTEXT", "WORD", "LTEXT"], 0, 0, shortcaption[n], 0, 0, caption[n], 1, iconpath, 0, "") rw.addresource(res) string = rw.tostring() del rw sw.addfile(string, loctarget) del string # Generate MIF file for icon. icontarget = "%s:\\resource\\apps\\%s_0x%08x_aif.mif" % ( drive, appname, uid3) mw = miffile.MIFWriter() mw.addfile(icondata) del icondata string = mw.tostring() del mw sw.addfile(string, icontarget) del string # Add files to SIS object. if len(srcfiles) == 1: # Read file. f = file(os.path.join(srcdir, srcfiles[0]), "rb") string = f.read(MAXOTHERFILESIZE + 1) f.close() if len(string) > MAXOTHERFILESIZE: raise ValueError("%s: input file too large" % srcfiles[0]) # Add file to the SIS object. One file only, rename it to default.py. target = "default.py" sw.addfile(string, "%s:\\private\\%08x\\%s" % (drive, uid3, target)) del string else: if extrasdir != None: sysbinprefix = os.path.join(extrasdir, "sys", "bin", "") else: sysbinprefix = os.path.join(os.sep, "sys", "bin", "") # More than one file, use original path names. for srcfile in srcfiles: # Read file. f = file(os.path.join(srcdir, srcfile), "rb") string = f.read(MAXOTHERFILESIZE + 1) f.close() if len(string) > MAXOTHERFILESIZE: raise ValueError("%s: input file too large" % srcfile) # Split path into components. srcpathcomp = srcfile.split(os.sep) targetpathcomp = [s.decode(filesystemenc) for s in srcpathcomp] # Check if the file is an E32Image (EXE or DLL). filecapmask = symbianutil.e32imagecaps(string) # Warn against common mistakes when dealing with E32Image files. if filecapmask != None: if not srcfile.startswith(sysbinprefix): # Warn against E32Image files outside /sys/bin. print ("%s: warning: %s is an E32Image (EXE or DLL) " "outside %s" % (pgmname, srcfile, sysbinprefix)) elif (symbianutil.ise32image(string) == "DLL" and (filecapmask & ~capmask) != 0x00000000L): # Warn about insufficient capabilities to load # a DLL from the PyS60 application. print ("%s: warning: insufficient capabilities to " "load %s" % (pgmname, srcfile)) # Handle the extras directory. if extrasdir != None and extrasdir == srcpathcomp[0]: # Path is rooted at the drive root. targetfile = u"%s:\\%s" % (drive, "\\".join(targetpathcomp[1:])) else: # Path is rooted at the application private directory. targetfile = u"%s:\\private\\%08x\\%s" % ( drive, uid3, "\\".join(targetpathcomp)) # Add file to the SIS object. sw.addfile(string, targetfile, capabilities = filecapmask) del string # Add target device dependency. sw.addtargetdevice(0x101f7961L, (0, 0, 0), None, ["Series60ProductID"] * numlang) # Add "Python for S60" dependency, version 1.4.0 onwards. # NOTE: Previous beta versions of Python for S60 had a # different UID3 (0xf0201510). sw.adddependency(0x2000b1a0L, (1, 4, 0), None, ["Python for S60"] * numlang) # Add certificate. sw.addcertificate(privkeydata, certdata, passphrase) # Generate an EXE stub and add it to the SIS object. string = execstubdata.decode("base-64") string = symbianutil.e32imagecrc(string, uid3, uid3, None, heapsizemin, heapsizemax, capmask) if runinstall: # To avoid running without dependencies, this has to be in the end. sw.addfile(string, exetarget, None, capabilities = capmask, operation = sisfield.EOpRun, options = sisfield.EInstFileRunOptionInstall) else: sw.addfile(string, exetarget, None, capabilities = capmask) del string # Generate SIS file out of the SimpleSISWriter object. sw.tofile(outfile) ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def scandefaults(filename): '''Scan a Python source file for application version string and UID3.''' version = None uid3 = None # Regular expression for the version string. Version may optionally # be enclosed in double or single quotes. version_ro = re.compile(r'SIS_VERSION\s*=\s*(?:(?:"([^"]*)")|' r"(?:'([^']*)')|(\S+))") # Original py2is uses a regular expression # r"SYMBIAN_UID\s*=\s*(0x[0-9a-fA-F]{8})". # This version is a bit more lenient. uid3_ro = re.compile(r"SYMBIAN_UID\s*=\s*(\S+)") # First match of each regular expression is used. f = file(filename, "rb") try: while version == None or uid3 == None: line = f.readline() if line == "": break if version == None: mo = version_ro.search(line) if mo: # Get first group that matched in the regular expression. version = filter(None, mo.groups())[0] if uid3 == None: mo = uid3_ro.search(line) if mo: uid3 = mo.group(1) finally: f.close() return version, uid3 def parseversion(version): '''Parse a version string: "v1.2.3" or similar. Initial "v" can optionally be a capital "V" or omitted altogether. Minor and build numbers can also be omitted. Separator can be a comma or a period.''' version = version.strip().lower() # Strip initial "v" or "V". if version[0] == "v": version = version[1:] if "." in version: parts = [int(n) for n in version.split(".")] else: parts = [int(n) for n in version.split(",")] # Allow missing minor and build numbers. parts.extend([0, 0]) return parts[0:3] def readtextfiles(pattern, languages): '''Read language dependent text files. Files are assumed to be in UTF-8 encoding and re-encoded in UCS-2 (UTF-16LE) for Symbian OS to display during installation.''' if "%" not in pattern: # Only one file, read it. filenames = [pattern] else: filenames = [] for langid in languages: langnum = symbianutil.langidtonum[langid] langname = symbianutil.langnumtoname[langnum] # Replace formatting characters in file name pattern. filename = pattern filename = filename.replace("%n", "%02d" % langnum) filename = filename.replace("%c", langid.lower()) filename = filename.replace("%C", langid.upper()) filename = filename.replace("%l", langname.lower()) filename = filename.replace("%L", langname) filename = filename.replace("%%", "%") filenames.append(filename) texts = [] for filename in filenames: f = file(filename, "r") # Read as text. text = f.read(MAXTEXTFILELENGTH + 1) f.close() if len(text) > MAXTEXTFILELENGTH: raise ValueError("%s: text file too large" % filename) texts.append(text.decode("UTF-8").encode("UTF-16LE")) return texts ############################################################################## # Embedded data: EXE stub, private key and icon, application resource ############################################################################## # This is the Symbian application stub, which starts the Python interpreter # and loads default.py. It is represented here as a base-64-encoded string. # The stub needs to be patched with correct UID3, Secure ID and capabilities. # After that, a couple of checksums need to be updated. Function # e32imagecrc(...) in module symbianutils takes care of all that. # # This stub will examine process SID and report that as the UID3 for the # GUI framework. Therefore, no code parts need to be patched at all. execstubdata = ''' egAAEM45ABAAAADwYNK4d0VQT0Oj9Bd6AAAKAPx6HxACAPkBAEIneG774AAqAAASKCEAAAAAAAAA EAAAAAAQAAAAAQAEAAAAuBQAAACAAAAAAEAABwAAAAAAAAAAAAAAKCEAAJwAAAAAAAAAxCEAAOgj AAAAAAAAXgEBIMgjAAAAAADwAAAAAAAAAAAAAAAAFRUAAAAAAAAAAAEA3o7XwWL6MS4Zb7Nz5hxm M3a2XWm2N37m49JhleXy7k44Y9dY67dNzGdDd5x0628RrtK9ddM6F5OZudY7nMtemcXvrrnMHraC 903douUxzbRUy3UO3MLqjUOdzpwvJek49OsVredfPVPvnnl8810uJ792wvVPfOHvfXr3PHJFEYsW MvtXxEqORv1dyZZbgK7mX4kMopJUVOAWbU9JnWNazmh/bThpUfy7+XuSKtnstFvM7vlJ1u3Exemb VKsdjK1jqpZoFmnekhlwFV807YsYjpMXzsZdlxVjNOwl76hGRnv5SZai4ujbLb4eP4vbkVhtv3bL 5v/7GXpk2XP5qmY3Dq5jefDLjG3wrkWlcSIuRhFQctj08kw8pXcV/b+2+YfkUl/Z1UIcUIcgIebJ lwlWXNMRcApLLSgS5A3/5Dz3AUYZaNZqhZqobdJXjBX5bLMua4J9gFvYB15DEBG+dYRVdgIV9KNe oIt+uDteTmnTz4g3OvJGUGuKBXQpyBlylt2AbdpJxAp6Nv+l9v6Nv84Tzf+QDesN+y2TucBshXEd 9UffOMaNvLl22PvVBKdDDOAbN6WVQHsFQBye9aMuPMX7cItI1DEd5AdtIbvTBsUOHdobdOw6eQaR BXsAFb1vGEQ+MyjpTl+kHf47yWZ3UlBPWrxidfWdvlc6ZqffKcfU7bLRPmKatMpsqQUD1Il+cI0h MekXXHQfVnKvYPyDzTutfP807x7c5zL5KhPKT9PnGXLWzEnyDzqnP+H/+cU5vxPXzUFWeUcHyaWT B9L6x356y+Pb7Gz6LKLA9nSjmcGxudIbGmxCbeAR1Xqz+9sp+3wPjtis2/bZQ37Kmb8dQUTsHSX8 1t/RyhtsCYNjLXZfwu+/TFM577hZRvut/gN90+56zjkN/ihftsvjVRYY7FMx/jhZXB9rKGHk6o+M vXQ45Af83GHr+OTnd9twv05QZ90y1WkK45bLXpQjsz6Z+zv38I63y4ExGeL056dXUhD557m2iHR/ 8mQ8i9+W/wn0fEd8MX1/GBu1AO+vuMWa3RmbnH0XeEX3Rn7IDkgrvFKcVDjvFJTVmbmJLIm1xnw3 2mXqyQblwaKQ/NYBtQHTxEMNUL8hnwn+Dyn+H9wfC39MTXgpQ0+E89v0jR5O5x7CEW/Key5FXHC2 MXGmFXOXghfXf8YLVSvfN/I89yJT7103bUoZ10qDLmWnwIby8JuYWZgR/c7Vqpn4LfCCPyZac4J9 pJH3JihXD75gqF66pRw8aj9bO743DKWdk++ThFPhKSsJRMRvPktJkacxlpPCfkGkuNx5dipbiiX8 L38TbWgini9kRarsyLsQDNXBYWSHcATqbXJjdupL7t1Mt2LZYKyv2is79pLT37Sl1gNnyWcY5Yba obdVK2ZVcrZqzCr3Clffj1BBz5XZOxDlvtNzjpBfka3n7t1QMeHQDHlARr1qoDTiyL1rLWN8IJg/ zd81DtL1qLb3zUKUODhvtiQZsNRWa33h978rDdOfP1hpkvu3TZ6YP4VxfNeoM/mByP6aEutF4vuj xnoS/RNLpb/CWe+4Qvb2tyy1uh96InPKTKf7XzjOlKGzsMpHM/3ab9fbRMYDqio+8n08ZSXgNvFc OKEZ/gQ6kH9ybfXEl1YPEvDBtSE/tH3HCeuE+TNbjL+WL++7fXbwiOGXChnXOFuNdNQLvrH/GeH7 lDmtiZQija0mArhLjseW6aP9hB3CXmUFV8AX84Kr6PUZ3fXLF632JzVnTGJx7Nia6mJs/whl8iKl yPTXZhRV5sMhn+om/5TzpjcuZ81uI/Hd+AbRT5rtzLmu+QmO/tfDdqC0jkUVpsM7Z3NsbZ3OT9s6 oXVNKhpyAhiX9dqBrn1v8IJaYbftCnPSihc2P06Z2F5TkgprvTn3Nq4bt0vBsfdo845yzPb8uHPu EOfrJM103L2oV5Yce4HG1GTrn/5cqB4/0pNq3GZmuqgZ8i7nRTprdthTW9jK9d0/RmcM/eyOLKOU xNbqF2Wj0+UGzvn90NrQh41Mh29MhrZQh72mQ6wP/SQ6wIdUM/3RrymD4PBQeeJNQPnLusPZ1DDN 6oLBdufJbB6ela6cZZbBxr0rXoPPdoKD3Is/DHnbsHIghmmOhe3RGOIGf7KUEUxCeFVEzXc0I1pA 7w3nqtWfhj1x6dzZkX5Te9J+2Oy1m0RXOKebHL/SJi4OaGZ2wdF7LliPuZ3YhDM/6A5do11xnnJ+ ks88mPZPSV58Vurn4LR/czs0ewFPdhPHIY5H+k/MvpqWBdzsTo7+7ucc9380Pv8h9Lwt9OGmmUD3 1q49/YV4xqbAgeJ7119s7pzhuIl1E8Unz5sO3Cnolz4npHNUxhx02b9W2RllnlYv1UmLXMxmJsci 6pzwhCONS1w55TmXJMqa3aCbvMCI92GenzlrQJXtgd9S5nTOy+ujDeONh17MTP8FEg+XEl7QjX8p zqjQ7rAoRrqa7DEp4w09jkQTJnMPrwbJRtaU5mFRDli3v5ps/owp0trs1xTWKX7Jea6mHT3qG3lg 2yn7V03DTLTtG59kHLUk7JuKGnwRXvFfsz5hzeg2ScK5R+Rea7/QeOMRj+MFT9A/GDpqhv3bEHJZ 7eovXDrHaiyfpGj7O0h+F6XOYXtn4M+G/dbZcUDh5tXaO5xjaOsL7R2yLaO9YjketRMHnKafP8A3 GwyaPhunv9cJ9iE+QMqr9mcL9eBf9eFvdiHbCs0Yh+soJFJb5APOwxhv/t/+Ab78w22X2XNCIhpj gefGU+486b4x+0NU6w5raSLad8kLZ4YcbFDDFzeIxiwf5fr2rcRefHJXCj1Yi/VDTnHP2f3VtWzr /UYwnapWeL4fbY0I6g1waxMdSshZLzh4K5jp0bn4Ez7z1T2+6g8lZYQcdma2cq2hYfnH31yRejDH Z0B018s2lKrm+QG10WCHg/0rmlH3wgWgM7BWwKcCugU8FrgXCCHig2Idhdcm0h4m0+ZvnKg6vxxv 8zFq3z6Jt/ZEI6o/HejurODfQzyfg3F8kCZNbOWb/b/KwRB+a7zwU5JRhXvhxwIcJjVqw2L8n4ri c9/t3RzOaPnwjnwPb03A/LPyVl9hzvX6w2XPYEo8vJIf593DlaVxXEMHYt1wvUDvqHdDVAO6ec2Y vrxwwzz6TbhsQadeNNx+gegO3Mes/PMuaTlmRZ/VcezybaX0b6upJ5u/44Qzdu9PDfZBZyb9pOqu HP9j2fPi430eI/0vUkwqD8A14HH9soIf+GNzp1UZxLCZrsELv8Yz+lG8/yQdf6v/MFpgXEEk9wfm XXJMSTzIkmmBmRgnhJczfQhJeuEo67eBZ+WEuSEvzAVujk8+W8uFM0+EEurzd9C88PHugd9MDv/s Fghy6k+1cbry4S6I57ONafMX7VLnuGOj94GnSiecOz5jfiuJV/7lBKvoAs5ghm+risOqP2jiuO8X UbzPO39Cpf8U813QA/qZWzfa5o/wnZnng/YUbrv+sGgcyd76OC3TzJxTv9ht9giPfKvH+AcpvTjd 9FWM31QWZxn++jX5Ea8JVl9y4N7qNnvOajXzJ/l/Ta/xI04qGP0hsdiM3bIY9RB403OgQfpg/Tpl f2hpneXqg9eWRT2c7fYCDdUibs8ZdUifP+t+YtlzOn6P3iI7NdvPwnTs1qD3zxaB+T/vH7D8KyFB a/nj9e5o81/rOpI9UalCa7VDT+EWy6VoeKHTxciC12oF3ww8+iLCBsGvS0+CNO3PTYnO8taDwppT B4O5jdI/7D60/pP7+IFvPxHcvPwaFgLRfV8B4PTyY/Pe4cP9hyIPuTP3g5f3gv+5UGnuuG9yNa/+ fYvJjrfKVw44v310pWbHrZwfPF89zyeGMEeL/cMM+HjLyfKcUo82LZWHj7rEIs3/gRJTCK67cn/l kP3bsz9r52Aztb+q9wLGTiO2MrEHrHuGrMfuGRbV1g/0/GLc8/hiWdm/+yZFc94PF73geejybt+U Bf6IL6lTAXioy7hBfpQV6nAXLoy/6oLwEF2iUBdKg/9VGmkgt2gt6gvWQUlBYaE/xEZYSCwUacqH AWrRl9dGv2aMvapocdBWqNfzig+XO2IqI83Hd4BFoxCX8QP+gOKd6Rb6sItkQjeHs8IadeCkAZ4D mgPpARhpx0xIu9RIsL/vQmTNnAv4xH4QCLkgM0gvoAcwkJpSDPCHy4N5kDnQt/uDP5YGmvsyBjjm e+BcgBYAe4888bTmg3nOdtZX3NhrNdrbbXanY/b1et2Az7wohLjXu60a9mjX6KXLu+t1Oo2HW67W kg7k7YPHWiccoAfx4fMgD5QOIB+sDhgfIBwgPjA+ID4QOCB8AH6gPfAoge8B7oH/oBvPwHh04A3w GOB7YHtAOgYwDgDYDQFAD2QPYA9cD1gP0geqBvQN4B6gG7A9MD9AH5wP/APSAxXoPhzPxCfx+PiY QqVAswLQh2lFBxBVBq6EeppjYr/TOCT5kmglEKcQ5k0Me0pl+ZTKZppUynEKBoPI+PoBFJKY88lM OxnxZBroS4xQLzUo+7OAkJdkaU+kPU38H/fs/tObSYaM8qY7e5ofzoUPeoX7Ji5mTfelCeSiJ/7n Xp+NwkH8M3HhQbfyiI35Sdj52KF0uhlRD1uO7H85c/+MnFKM8E0CpZCKx8l8y9hBIt8uM/EkMjOH Bfs11VAIYFSBEAqgCtyViU3TfKYj1CIxmf1SKi6xFLeWAy6NyZhFmc561iFuMhbjRarsEWr7EDQo prPiHRAdGB0wHTgdQiovqSH6ykPeKBcAaYD+UhhbA1BQUj6oDVgSwNiiLp26ztx35QXb9mBgAbQD tQO2IYvQMQ6gwP8QP8gO+A/CAeHM8T/jAeSB5QHlgeYAeG88VfsAeyA0AfZJRA98D9QHBA4YH6wO IB8oGQcq0O/5CQypDd/f8WqckOWcZ7ctcN92xa8uv/Z1DpVB0/Vc/lKdGl0PfQ1IIR/TIYW0YL08 ly3k5+JiJX9CSLI/F1n6DDwX7o+F6ElQ0uTJE/dPP+zDg530QHPpwc7n2PnebyyGv2vSrqq6NdXM AcTql1cyCXgetWw/kJX6AHUIT9IiZc+c/rlxbKFK7Xdj/5j0NDAPtg3M/TJXqQWgBeeCkguzBWQL t8Hk1TVzhT8j0BK8zA/dHeDBn+M7o7z1YfePnfqy9dqc7ptfd82XmzV1fqJE+OfC3UfoL0pfVfeE TV3whZ3Sd6WyS76dOnP5/R90lezp0zA+em2r5t7gPuXwHrzAGZOGYL5sSyUtCuFPmndOb++tvCNn 9G+z5jxVqUSQpYpWcUiRfR/fV//ETZ3gZwX17YtqO+T0H6L9UXg+3BTHZ0HrydO28yS+XfzLkFhZ D154ILa5AO2nrzuAVsC7sFrATOQAC70Eo29ebgFcgvFPTBePkA0FPBbwF5QKK49eboFJBecC7IFj AtuCxzPwXuGv8Z68ogrEFwQVoC4WQG2C4gKI6HTHR+W6EN4NN0Z7wZ/tAAtOCVeDTBHTFSLde1Bw ey9synTLgyLZTMIy/ElM4mOC8BOZP3wLw+SmeV7oa+yUmY/uwI9n7xn8KZI+CDTufgg07l/KUzWI Jn4BwP5Q6fBg2exP0/pQWi9ksJr1hcvWDrrC22B48cTVItH+sDe5SgwD7AYMO4CZ2EMdukMZVhZX AwVhcHWFUdeL+deGXXj7xxVulBg3sq8V314S44Q44bccez8DcAfojix/HHqnjja44a6wR1hbfWCu LQvopQW3+wGXslht6wU1haLWHs3A26wvfrCJYGKsF+BvFg+4FBYew9YMtYV5gcJGLCLXiwvXjFeB 4McK8cP/HGIMa/vI4XLBs9YQfjhrxxBuOKI1glrCH6w7b1hwsC2WF88CtTghfGWGO+VK6LSWv9jD DGoYHz+37POD/2g1Gg1XV562t7coPbH/PqJcvY9bxTzPaq7uy1HX/a12tyShzvPannurf1dVr9hq NXqOsyaZq+t+1qddr9Xkk6ys9Dn+rg19j12r1+Tc3Q6vnYNXrtTLl63+rVXeSYmwP8L0X97Y5p1e yVoK4yun5S65XVZnccrZ10WOrHx4/uR7ZaitrK/u5DMjvZG3keDInSNxI8WR48ifI8qRupHnSIL5 jBwbjyI9hHXj6yPdx9UtrFrtbsVvBW3C3ireOtjLKV8ivs6/T19zyX//''' # Python logo as a base-64-encoded, zlib-compressed SVG XML data defaulticondata = ''' eJyFVF1v20YQfC/Q/3Bl0be74+3tfQZRA1h24gJJa6COij66EiMSdSVDUiW3vz5zRyp2CgMVLHrJ /ZjZmRNfv3n8614cu91+2G5mDWnTiG6z3K6GzXrWfLx9q1Lz5sdvv3n93eUv89vfb67E/rgWNx8v 3v80F41q29943raXt5fi18U7QZrE7bD5p22vfm5E0x8OD6/a9nQ66RPr7W7dvtvdPfTDct+iukV1 6WwxkUgd0KdXh1VT0ArIH3f77ma3/TTcd7OmZJvnPKkRYL7Zv9qD6wO+szPc+YHeb//eLbtPwO30 pjuMWFNSmYo1zpi9wNQaYwqzM8zj/bD586VCyjm3NduI07A69GBnzA+N6Lth3R/Od8ehO11sH2eN EUYEh7+66LpcHu4OvcCe97Pmew4hZ9eI1az5QEln5yVZ7YxfGsXaJyeNzoGU156dDNqGpIJ2gZes g2HsFZhk0tZaxJFKt8cTI4RAiTOMAT7Y2pola5PjFNcxC+u05aVBxmG01TERMkzqXMR0bb22ZJcK pd5Ko6JOHNERbJjiqKP1R69DdD3K2OSCjw2CwwZgH0PA8GAL++DLlbGjIm2NRUN2CMlTGVd2Vlgj EETQOSfkyUaJa5oaZUHlMe6djoavmbRLR0zxsWBfj2IuRjHffyXtv037Xxuu4oVnM9rgnDZkpTfa ulSVgQ1YhYik15zTkzRlBcC7LElz8CpBaliATYJ6bgS6mbwqVgY1mmh15qzOhmLSgpN21lbfnbHS FmFLir7YztSPU5fQIkKmqkMsMpswxUVAeyznhQKkwekaT0JwCfXgHyJGR5qmTlvAB89QOOdCH/bh SEVbECYjilOoZh1jqka6sVM9m9KPN5MVxYkE7InyYtTzBe3f1s+ovbXaRKhJQIASVLbHS8plYDJw cPVjWChnD4K4q/obn6a45svWpu7iVUm6+pjVUwnPLUy1SRLrnFieseGM9fI5k/8hzQFuZOkBwzyy xhGtoJWre6LtKu080q41YYxq8olzrpypPo7qS0Wcc8TPFYcTZ0SecctKVn7FYmLc1vdNea/h/2dD N2YO''' # "Python for S60" compiled resource as a base-64-encoded, zlib-compressed data pythons60rscdata = ''' eJzL9pIXYACCVnFWhouea4oYtRk4QHwWIGYMqAgEs6E0CEgxnOGAsRnYGRlYgXKcnK4VJal5xZn5 eYJg8f9AwDDkgQSDAhDaMCQypDPkMhQzVDLUM7QydDNMZJjOMJdhMcNKhvUMWxl2MxxkOM5wluEy w02G+wxPGV4zfGT4zvCXgZmRk5GfUZRRmhEAjnEjdg==''' ensymble-0.29/ensymble/actions/infoe32.py0000664000076400007640000001255711373531772017452 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # cmd_infoe32.py - Ensymble command line tool, infoe32 command # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import sys import os import getopt import locale import struct from utils import symbianutil ############################################################################## # Help texts ############################################################################## shorthelp = 'Show the IDs and capabilities of e32image files (EXEs, DLLs)' longhelp = '''infoe32 [--encoding=terminal,filesystem] [--verbose] ... Show the IDs and capabilities of e32image files (Symbian OS EXEs and DLLs). Options: infile - Path of the e32image file/files encoding - Local character encodings for terminal and filesystem verbose - Not used ''' ############################################################################## # Parameters ############################################################################## MAXE32FILESIZE = 1024 * 1024 * 8 # Eight megabytes ############################################################################## # Global variables ############################################################################## debug = False ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python MAXE32FILESIZE: raise ValueError("input e32image file too large") # Get info about the e32image try: (uid1, uid2, uid3, sid, vid, capmask) = symbianutil.e32imageinfo(instring) caps = symbianutil.capmasktostring(capmask, True) print "%s:" % infile print " UID1 0x%08x" % uid1 print " UID2 0x%08x" % uid2 print " UID3 0x%08x" % uid3 print " Secure ID 0x%08x" % sid print " Vendor ID 0x%08x" % vid print " Capabilities 0x%x (%s)" % (capmask, caps) except ValueError: raise ValueError("%s: not a valid e32image file" % infile) ensymble-0.29/ensymble/actions/altere32.py0000664000076400007640000002653411373531772017626 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # cmd_signsis.py - Ensymble command line tool, altere32 command # Copyright 2006, 2007, 2008 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import sys import os import getopt import locale import struct from utils import symbianutil ############################################################################## # Help texts ############################################################################## shorthelp = 'Alter the IDs and capabilities of e32image files (EXEs, DLLs)' longhelp = '''altere32 [--uid=0x01234567] [--secureid=0x01234567] [--vendorid=0x01234567] [--caps=Cap1+Cap2+...] [--heapsize=min,max] [--inplace] [--encoding=terminal,filesystem] [--verbose] [outfile] Alter the IDs, capabilities and heap sizes of e32image files (Symbian OS EXEs and DLLs). Options: infile - Path of the original e32image file (or many, if --inplace set) outfile - Path of the modified e32image file (not used with --inplace) uid - Symbian OS UID for the e32image secureid - Secure ID for the e32image (should normally be same as UID) vendorid - Vendor ID for the e32image caps - Capability names, separated by "+" heapsize - Heap size, minimum and/or maximum (not altered by default) inplace - Allow more than one input file, modify input files in-place encoding - Local character encodings for terminal and filesystem verbose - Print extra statistics When modifying the UID, the secure ID should be modified accordingly. Modifying UIDs of application EXEs is generally not possible, because applications usually include the UID in program code as well. ''' ############################################################################## # Parameters ############################################################################## MAXE32FILESIZE = 1024 * 1024 * 8 # Eight megabytes ############################################################################## # Global variables ############################################################################## debug = False ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python heapsizemax: print ("%s: warning: minimum heap size larger than " "maximum heap size" % pgmname) else: heapsizemin = None heapsizemax = None # Determine parameter format. Modifying files in-place or not. inplace = False if "--inplace" in opts.keys() or "-i" in opts.keys(): inplace = True # Determine e32image input / output file names. files = [name.decode(terminalenc).encode(filesystemenc) for name in pargs] if not inplace: if len(files) == 2: if os.path.isdir(files[1]): # Output to directory, use input file name. files[1] = os.path.join(files[1], os.path.basename(files[0])) else: raise ValueError("wrong number of arguments") # Determine verbosity. verbose = False if "--verbose" in opts.keys() or "-v" in opts.keys(): verbose = True # Determine if debug output is requested. if "--debug" in opts.keys(): debug = True # Ingredients for successful e32image file alteration: # # terminalenc Terminal character encoding (autodetected) # filesystemenc File system name encoding (autodetected) # files File names of e32image files, filesystemenc encoded # uid3 Application UID3, long integer or None # secureid Secure ID, long integer or None # vendorid Vendor ID, long integer or None # caps, capmask Capability names and bitmask or None # heapsizemin Heap that must be available for the app. to start or None # heapsizemax Maximum amount of heap the app. can allocate or None # inplace Multiple input files or single input / output pair # verbose Boolean indicating verbose terminal output if verbose: print if not inplace: print "Input e32image file %s" % ( files[0].decode(filesystemenc).encode(terminalenc)) print "Output e32image file %s" % ( files[1].decode(filesystemenc).encode(terminalenc)) else: print "Input e32image file(s) %s" % " ".join( [f.decode(filesystemenc).encode(terminalenc) for f in files]) if uid3 != None: print "UID 0x%08x" % uid3 else: print "UID " if secureid != None: print "Secure ID 0x%08x" % secureid else: print "Secure ID " if vendorid != None: print "Vendor ID 0x%08x" % vendorid else: print "Vendor ID " if caps != None: print "Capabilities 0x%x (%s)" % (capmask, caps) else: print "Capabilities " if heapsizemin != None: print "Heap size in bytes %d, %d" % (heapsizemin, heapsizemax) else: print "Heap size in bytes " print if ((uid3, secureid, vendorid, caps, heapsizemin) == (None, None, None, None, None)): print "%s: no options set, doing nothing" % pgmname return for infile in files: # Read input e32image file. f = file(infile, "rb") instring = f.read(MAXE32FILESIZE + 1) f.close() if len(instring) > MAXE32FILESIZE: raise ValueError("input e32image file too large") # Modify the e32image header. try: outstring = symbianutil.e32imagecrc(instring, uid3, secureid, vendorid, heapsizemin, heapsizemax, capmask) except ValueError: raise ValueError("%s: not a valid e32image file" % infile) if not inplace: outfile = files[1] else: outfile = infile # Write output e32image file. f = file(outfile, "wb") f.write(outstring) f.close() if not inplace: # While --inplace is not in effect, files[1] is the output # file name, so must stop after one iteration. break ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def parseuid(pgmname, uid): if uid.lower().startswith("0x"): # Prefer hex UIDs with leading "0x". uid = long(uid, 16) else: try: if len(uid) == 8: # Assuming hex UID even without leading "0x". print ('%s: warning: assuming hex UID even ' 'without leading "0x"' % pgmname) uid = long(uid, 16) else: # Decimal UID. uid = long(uid) print ('%s: warning: decimal UID converted to 0x%08x' % (pgmname, uid)) except ValueError: raise ValueError("invalid UID string '%s'" % uid) return uid ensymble-0.29/ensymble/actions/signsis.py0000664000076400007640000004317211373531772017661 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # cmd_signsis.py - Ensymble command line tool, signsis command # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import sys import os import getopt import getpass import locale import struct from hashlib import sha1 from utils import sisfile from utils import sisfield from utils import symbianutil from utils import cryptutil ############################################################################## # Help texts ############################################################################## shorthelp = 'Sign a SIS package' longhelp = '''signsis [--unsign] [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] [--execaps=Cap1+Cap2+...] [--dllcaps=Cap1+Cap2+...] [--encoding=terminal,filesystem] [--verbose] [outfile] Sign a SIS file with the certificate provided (stripping out any existing certificates, if any). Optionally modify capabilities of all EXE and DLL files contained in the SIS package. Options: infile - Path of the original SIS file outfile - Path of the signed SIS file (or the original is overwritten) unsign - Remove all signatures from SIS file instead of signing cert - Certificate to use for signing (PEM format) privkey - Private key of the certificate (PEM format) passphrase - Pass phrase of the private key (insecure, use stdin instead) execaps - Capability names, separated by "+" (not altered by default) dllcaps - Capability names, separated by "+" (not altered by default) encoding - Local character encodings for terminal and filesystem verbose - Print extra statistics If no certificate and its private key are given, a default self-signed certificate is used to sign the SIS file. Software authors are encouraged to create their own unique certificates for SIS packages that are to be distributed. Embedded SIS files are ignored, i.e their certificates are not modified. Also, capabilities of EXE and DLL files inside embedded SIS files are not affected. ''' ############################################################################## # Parameters ############################################################################## MAXPASSPHRASELENGTH = 256 MAXCERTIFICATELENGTH = 65536 MAXPRIVATEKEYLENGTH = 65536 MAXSISFILESIZE = 1024 * 1024 * 8 # Eight megabytes ############################################################################## # Global variables ############################################################################## debug = False ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python MAXCERTIFICATELENGTH: raise ValueError("certificate file too large") # Read private key file. f = file(privkey, "rb") privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1) f.close() if len(privkeydata) > MAXPRIVATEKEYLENGTH: raise ValueError("private key file too large") elif cert == None and privkey == None: # No certificate given, use the Ensymble default certificate. # defaultcert.py is not imported when not needed. This speeds # up program start-up a little. from utils import defaultcert certdata = defaultcert.cert privkeydata = defaultcert.privkey print ("%s: warning: no certificate given, using " "insecure built-in one" % pgmname) else: raise ValueError("missing certificate or private key") # Get pass phrase. Pass phrase remains in terminal encoding. passphrase = opts.get("--passphrase", opts.get("-p", None)) if passphrase == None and privkey != None: # Private key given without "--passphrase" option, ask it. if sys.stdin.isatty(): # Standard input is a TTY, ask password interactively. passphrase = getpass.getpass("Enter private key pass phrase:") else: # Not connected to a TTY, read stdin non-interactively instead. passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1) if len(passphrase) > MAXPASSPHRASELENGTH: raise ValueError("pass phrase too long") passphrase = passphrase.strip() # Get EXE capabilities and normalize the names. execaps = opts.get("--execaps", opts.get("-b", None)) if execaps != None: execapmask = symbianutil.capstringtomask(execaps) execaps = symbianutil.capmasktostring(execapmask, True) else: execapmask = None # Get DLL capabilities and normalize the names. dllcaps = opts.get("--dllcaps", opts.get("-d", None)) if dllcaps != None: dllcapmask = symbianutil.capstringtomask(dllcaps) dllcaps = symbianutil.capmasktostring(dllcapmask, True) else: dllcapmask = None # Determine verbosity. verbose = False if "--verbose" in opts.keys() or "-v" in opts.keys(): verbose = True # Determine if debug output is requested. if "--debug" in opts.keys(): debug = True # Enable debug output for OpenSSL-related functions. cryptutil.setdebug(True) # Ingredients for successful SIS generation: # # terminalenc Terminal character encoding (autodetected) # filesystemenc File system name encoding (autodetected) # infile Input SIS file name, filesystemenc encoded # outfile Output SIS file name, filesystemenc encoded # cert Certificate in PEM format # privkey Certificate private key in PEM format # passphrase Pass phrase of priv. key, terminalenc encoded string # execaps, execapmask Capability names and bitmask for EXE files or None # dllcaps, dllcapmask Capability names and bitmask for DLL files or None # verbose Boolean indicating verbose terminal output if verbose: print print "Input SIS file %s" % ( infile.decode(filesystemenc).encode(terminalenc)) print "Output SIS file %s" % ( outfile.decode(filesystemenc).encode(terminalenc)) if unsign: print "Remove signatures Yes" else: print "Certificate %s" % ((cert and cert.decode(filesystemenc).encode(terminalenc)) or "") print "Private key %s" % ((privkey and privkey.decode(filesystemenc).encode(terminalenc)) or "") if execaps != None: print "EXE capabilities 0x%x (%s)" % (execapmask, execaps) else: print "EXE capabilities " if dllcaps != None: print "DLL capabilities 0x%x (%s)" % (dllcapmask, dllcaps) else: print "DLL capabilities " print # Read input SIS file. f = file(infile, "rb") instring = f.read(MAXSISFILESIZE + 1) f.close() if len(instring) > MAXSISFILESIZE: raise ValueError("input SIS file too large") # Convert input SIS file to SISFields. uids = instring[:16] # UID1, UID2, UID3 and UIDCRC insis, rlen = sisfield.SISField(instring[16:], False) # Ignore extra bytes after SIS file. if len(instring) > (rlen + 16): print ("%s: warning: %d extra bytes after input SIS file (ignored)" % (pgmname, (len(instring) - (rlen + 16)))) # Try to release some memory early. del instring # Check if there are embedded SIS files. Warn if there are. if len(insis.Data.DataUnits) > 1: print ("%s: warning: input SIS file contains " "embedded SIS files (ignored)" % pgmname) # Modify EXE- and DLL-files according to new capabilities. if execaps != None or dllcaps != None: # Generate FileIndex to SISFileDescription mapping. sisfiledescmap = mapfiledesc(insis.Controller.Data.InstallBlock) exemods, dllmods = modifycaps(insis, sisfiledescmap, execapmask, dllcapmask) print ("%s: %d EXE-files will be modified, " "%d DLL-files will be modified" % (pgmname, exemods, dllmods)) # Temporarily remove the SISDataIndex SISField from SISController. ctrlfield = insis.Controller.Data didxfield = ctrlfield.DataIndex ctrlfield.DataIndex = None if not unsign: # Remove old signatures. if len(ctrlfield.getsignatures()) > 0: print ("%s: warning: removing old signatures " "from input SIS file" % pgmname) ctrlfield.setsignatures([]) # Calculate a signature of the modified SISController. string = ctrlfield.tostring() string = sisfield.stripheaderandpadding(string) signature, algoid = sisfile.signstring(privkeydata, passphrase, string) # Create a SISCertificateChain SISField from certificate data. sf1 = sisfield.SISBlob(Data = cryptutil.certtobinary(certdata)) sf2 = sisfield.SISCertificateChain(CertificateData = sf1) # Create a SISSignature SISField from calculated signature. sf3 = sisfield.SISString(String = algoid) sf4 = sisfield.SISSignatureAlgorithm(AlgorithmIdentifier = sf3) sf5 = sisfield.SISBlob(Data = signature) sf6 = sisfield.SISSignature(SignatureAlgorithm = sf4, SignatureData = sf5) # Create a new SISSignatureCertificateChain SISField. sa = sisfield.SISArray(SISFields = [sf6]) sf7 = sisfield.SISSignatureCertificateChain(Signatures = sa, CertificateChain = sf2) # Set new certificate. ctrlfield.Signature0 = sf7 else: # Unsign, remove old signatures. ctrlfield.setsignatures([]) # Restore data index. ctrlfield.DataIndex = didxfield # Convert SISFields to string. outstring = insis.tostring() # Write output SIS file. f = file(outfile, "wb") f.write(uids) f.write(outstring) f.close() ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def modifycaps(siscontents, sisfiledescmap, execapmask, dllcapmask): '''Scan SISData SISFields for EXE- and DLL-files and modify their headers for the new capabilities.''' # Prepare UID1 strings for EXE and DLL. exeuids = struct.pack(" [sisfile] Create a SIS package from a directory structure. Only supports very simple SIS files. There is no support for conditionally included files, dependencies etc. Options: srcdir - Source directory sisfile - Path of the created SIS file uid - Symbian OS UID for the SIS package version - SIS package version: X.Y.Z or X,Y,Z (major, minor, build) lang - Comma separated list of two-character language codes caption - Comma separated list of package names in all languages drive - Drive where the package will be installed (any by default) textfile - Text file (or pattern, see below) to display during install cert - Certificate to use for signing (PEM format) privkey - Private key of the certificate (PEM format) passphrase - Pass phrase of the private key (insecure, use stdin instead) vendor - Vendor name or a comma separated list of names in all lang. encoding - Local character encodings for terminal and filesystem verbose - Print extra statistics If no certificate and its private key are given, a default self-signed certificate is used to sign the SIS file. Software authors are encouraged to create their own unique certificates for SIS packages that are to be distributed. Text to display uses UTF-8 encoding. The file name may contain formatting characters that are substituted for each selected language. If no formatting characters are present, the same text will be used for all languages. %% - literal % %n - language number (01 - 99) %c - two-character language code in lowercase letters %C - two-character language code in capital letters %l - language name in English, using only lowercase letters %l - language name in English, using mixed case letters ''' ############################################################################## # Parameters ############################################################################## MAXPASSPHRASELENGTH = 256 MAXCERTIFICATELENGTH = 65536 MAXPRIVATEKEYLENGTH = 65536 MAXFILESIZE = 1024 * 1024 * 8 # Eight megabytes MAXTEXTFILELENGTH = 1024 ############################################################################## # Global variables ############################################################################## debug = False ############################################################################## # Public module-level functions ############################################################################## def run(pgmname, argv): global debug # Determine system character encodings. try: # getdefaultlocale() may sometimes return None. # Fall back to ASCII encoding in that case. terminalenc = locale.getdefaultlocale()[1] + "" except TypeError: # Invalid locale, fall back to ASCII terminal encoding. terminalenc = "ascii" try: # sys.getfilesystemencoding() was introduced in Python v2.3 and # it can sometimes return None. Fall back to ASCII if something # goes wrong. filesystemenc = sys.getfilesystemencoding() + "" except (AttributeError, TypeError): filesystemenc = "ascii" try: gopt = getopt.gnu_getopt except: # Python MAXCERTIFICATELENGTH: raise ValueError("certificate file too large") # Read private key file. f = file(privkey, "rb") privkeydata = f.read(MAXPRIVATEKEYLENGTH + 1) f.close() if len(privkeydata) > MAXPRIVATEKEYLENGTH: raise ValueError("private key file too large") elif cert == None and privkey == None: # No certificate given, use the Ensymble default certificate. # defaultcert.py is not imported when not needed. This speeds # up program start-up a little. from utils import defaultcert certdata = defaultcert.cert privkeydata = defaultcert.privkey print ("%s: warning: no certificate given, using " "insecure built-in one" % pgmname) # Warn if the UID is in the protected range. # Resulting SIS file will probably not install. if puid < 0x80000000L: print ("%s: warning: UID is in the protected range " "(0x00000000 - 0x7ffffff)" % pgmname) else: raise ValueError("missing certificate or private key") # Get pass phrase. Pass phrase remains in terminal encoding. passphrase = opts.get("--passphrase", opts.get("-p", None)) if passphrase == None and privkey != None: # Private key given without "--passphrase" option, ask it. if sys.stdin.isatty(): # Standard input is a TTY, ask password interactively. passphrase = getpass.getpass("Enter private key pass phrase:") else: # Not connected to a TTY, read stdin non-interactively instead. passphrase = sys.stdin.read(MAXPASSPHRASELENGTH + 1) if len(passphrase) > MAXPASSPHRASELENGTH: raise ValueError("pass phrase too long") passphrase = passphrase.strip() # Determine verbosity. verbose = False if "--verbose" in opts.keys() or "-v" in opts.keys(): verbose = True # Determine if debug output is requested. if "--debug" in opts.keys(): debug = True # Enable debug output for OpenSSL-related functions. import cryptutil cryptutil.setdebug(True) # Ingredients for successful SIS generation: # # terminalenc Terminal character encoding (autodetected) # filesystemenc File system name encoding (autodetected) # basename Base for generated file names on host, filesystemenc encoded # srcdir Directory of source files, filesystemenc encoded # srcfiles List of filesystemenc encoded source file names in srcdir # outfile Output SIS file name, filesystemenc encoded # puid Package UID, long integer # version A triple-item tuple (major, minor, build) # lang List of two-character language codes, ASCII strings # caption List of Unicode package captions, one per language # drive Installation drive letter or "!" # textfile File name pattern of text file(s) to display during install # texts Actual texts to display during install, one per language # cert Certificate in PEM format # privkey Certificate private key in PEM format # passphrase Pass phrase of private key, terminalenc encoded string # vendor List of Unicode vendor names, one per language # verbose Boolean indicating verbose terminal output if verbose: print print "Input files %s" % " ".join( [s.decode(filesystemenc).encode(terminalenc) for s in srcfiles]) print "Output SIS file %s" % ( outfile.decode(filesystemenc).encode(terminalenc)) print "UID 0x%08x" % puid print "Version %d.%d.%d" % ( version[0], version[1], version[2]) print "Language(s) %s" % ", ".join(lang) print "Package caption(s) %s" % ", ".join( [s.encode(terminalenc) for s in caption]) print "Install drive %s" % ((drive == "!") and "" or drive) print "Text file(s) %s" % ((textfile and textfile.decode(filesystemenc).encode(terminalenc)) or "") print "Certificate %s" % ((cert and cert.decode(filesystemenc).encode(terminalenc)) or "") print "Private key %s" % ((privkey and privkey.decode(filesystemenc).encode(terminalenc)) or "") print "Vendor name(s) %s" % ", ".join( [s.encode(terminalenc) for s in vendor]) print # Generate SimpleSISWriter object. sw = sisfile.SimpleSISWriter(lang, caption, puid, version, vendor[0], vendor) # Add text file or files to the SIS object. Text dialog is # supposed to be displayed before anything else is installed. if len(texts) == 1: sw.addfile(texts[0], operation = sisfield.EOpText) elif len(texts) > 1: sw.addlangdepfile(texts, operation = sisfield.EOpText) # Add files to SIS object. sysbinprefix = os.path.join("sys", "bin", "") for srcfile in srcfiles: # Read file. f = file(os.path.join(srcdir, srcfile), "rb") string = f.read(MAXFILESIZE + 1) f.close() if len(string) > MAXFILESIZE: raise ValueError("input file too large") # Check if the file is an E32Image (EXE or DLL). caps = symbianutil.e32imagecaps(string) if caps != None and not srcfile.startswith(sysbinprefix): print ("%s: warning: %s is an E32Image (EXE or DLL) outside %s%s" % (pgmname, srcfile, os.sep, sysbinprefix)) # Add file to the SIS object. target = srcfile.decode(filesystemenc).replace(os.sep, "\\") sw.addfile(string, "%s:\\%s" % (drive, target), capabilities = caps) del string # Add target device dependency. sw.addtargetdevice(0x101f7961L, (0, 0, 0), None, ["Series60ProductID"] * numlang) # Add certificate. sw.addcertificate(privkeydata, certdata, passphrase) # Generate SIS file out of the SimpleSISWriter object. sw.tofile(outfile) ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def parseversion(version): '''Parse a version string: "v1.2.3" or similar. Initial "v" can optionally be a capital "V" or omitted altogether. Minor and build numbers can also be omitted. Separator can be a comma or a period.''' version = version.strip().lower() # Strip initial "v" or "V". if version[0] == "v": version = version[1:] if "." in version: parts = [int(n) for n in version.split(".")] else: parts = [int(n) for n in version.split(",")] # Allow missing minor and build numbers. parts.extend([0, 0]) return parts[0:3] def readtextfiles(pattern, languages): '''Read language dependent text files. Files are assumed to be in UTF-8 encoding and re-encoded in UCS-2 (UTF-16LE) for Symbian OS to display during installation.''' if "%" not in pattern: # Only one file, read it. filenames = [pattern] else: filenames = [] for langid in languages: langnum = symbianutil.langidtonum[langid] langname = symbianutil.langnumtoname[langnum] # Replace formatting characters in file name pattern. filename = pattern filename = filename.replace("%n", "%02d" % langnum) filename = filename.replace("%c", langid.lower()) filename = filename.replace("%C", langid.upper()) filename = filename.replace("%l", langname.lower()) filename = filename.replace("%L", langname) filename = filename.replace("%%", "%") filenames.append(filename) texts = [] for filename in filenames: f = file(filename, "r") # Read as text. text = f.read(MAXTEXTFILELENGTH + 1) f.close() if len(text) > MAXTEXTFILELENGTH: raise ValueError("%s: text file too large" % filename) texts.append(text.decode("UTF-8").encode("UTF-16LE")) return texts ensymble-0.29/ensymble/__init__.py0000664000076400007640000000326511374343415016274 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # Copyright 2006, 2007, 2008, 2009 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## """ The Ensymble developer utilities for Symbian OS™ is a collection of Python® modules and command line programs for Symbian OS software development. Current focus of Ensymble development is to provide useful tools for making Python for S60 (PyS60) programs. A long term goal of Ensymble is to provide a cross-platform, open-source way to do Symbian OS software development, supporting Symbian OS versions 9.1 and later. SIS files made with Ensymble work from S60 3rd Edition phones onwards. For 1st and 2nd Edition phones there's py2sisng. """ __version__ = '0.29' __license__ = 'GPLv2' __author__ = 'Jussi Ylänen' __email__ = 'steve@lonetwin.net' __maintainer__ = 'Steven Fernandez' ensymble-0.29/ensymble/utils/0000775000076400007640000000000011374344735015323 5ustar stevesteveensymble-0.29/ensymble/utils/__init__.py0000664000076400007640000000000011373531772017420 0ustar stevesteveensymble-0.29/ensymble/utils/miffile.py0000664000076400007640000000503011373531772017304 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # miffile.py - Symbian OS multi-image format (MIF) utilities # Copyright 2006, 2007 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import struct ############################################################################## # MIFWriter class for grouping SVG-T items into MIF files ############################################################################## class MIFWriter(object): '''A MIF file generator Limitations: - MBM file linkage is not supported. - Flags and other unknown fields are filled with guessed values.''' def __init__(self): self.fileinfo = [] self.filedata = [] def addfile(self, contents, animate = False): self.filedata.append(contents) self.fileinfo.append((animate and 1) or 0) def tostring(self): # Generate header. strdata = ["B##4", struct.pack("= 0x80000000L: initialvalue -= 0x100000000L initialvalue = int(initialvalue) value = long(zlib.crc32(data, initialvalue)) if value < 0: value += 0x100000000L return value ^ finalxor def uidcrc(uid1, uid2, uid3): '''Calculate a Symbian OS UID checksum.''' # Convert UIDs to a string and group even and odd characters # into separate strings (in a Python v2.2 compatible way). uidstr = struct.pack(" len(negstring): return negstring return posstring def capmasktorawdata(capmask): '''Convert capability bit mask to raw four- or eight-character string.''' if capmask < (1L << 32): return struct.pack(" contents fromstring a SISField string contents SISField contents without the header and padding''' # Parse field header. ftype, hdrlen, flen, padlen = parsesisfieldheader(fromstring) # Return field contents. return fromstring[hdrlen:(hdrlen + flen)] ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def parsesisfieldheader(string, requiredtype = None, exactlength = True): '''Parse the header of a SISField string and return the field type and lengths of the various parts (header, data, padding). Optionally, check that the type is correct and that the string length is not too long.''' hdrlen = 8 if hdrlen > len(string): raise SISException("not enough data for a complete SISField header") # Get SISField type and first part of the length. ftype, flen = struct.unpack(" len(string): raise SISException("not enough data for a complete SISField header") flen2 = struct.unpack(" len(string): raise SISException("SISField contents too short") # Allow oversized strings when parsing recursive SISFields. if exactlength and (hdrlen + flen + padlen) < len(string): raise SISException("SISField contents too long") return ftype, hdrlen, flen, padlen def makesisfieldheader(fieldtype, fieldlen): '''Create a SISField header string from type and length.''' if fieldlen < 0x80000000L: # 31-bit length return struct.pack("> 31 fieldlen = (fieldlen & 0x7fffffffL) | 0x80000000L return struct.pack(" 0: # DEBUG: Print class name during initialization. print "%s.__init__()" % self.__class__.__name__ # Get the names of all instance variables. validkwds = self.__dict__.keys() # Filter out private instance variables (all in lowercase). validkwds = filter(lambda s: s != s.lower(), validkwds) # Set type code. self.fieldtype = fieldnametonum[self.__class__.__name__] if "fromstring" in kwds: if DEBUG > 1: # DEBUG: Dump of string parameter. print repr(kwds["fromstring"]) # Load instance variables from string. if len(kwds) != 1: raise TypeError( "keyword 'fromstring' may not be given with other keywords") self.fromstring(kwds["fromstring"]) else: # Load instance variables from keywords. # Only accept existing variable names. for kwd in kwds.keys(): if kwd not in validkwds: raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, kwd)) self.__dict__[kwd] = kwds[kwd] def __str__(self): # Default __str__() for SISFields, only return the field name. return "<%s>" % self.__class__.__name__ class SISFieldNormal(SISFieldBase): '''SISField base class for normal fields (fields containing only other fields and integers)''' # Subfield types FTYPE_INTEGRAL = 0 # Integer FTYPE_MANDATORY = 1 # Mandatory SISField FTYPE_OPTIONAL = 2 # Optional SISField FTYPE_ARRAY = 3 # SISArray with zero or more items def __init__(self, **kwds): # Initialize instance variables to None. for fattr, fkind, ffmt in self.subfields: self.__dict__[fattr] = None # Set instance variables. SISFieldBase.__init__(self, **kwds) for fattr, fkind, ffmt in self.subfields: # Check that all required instance variables are set. if fkind != self.FTYPE_OPTIONAL and self.__dict__[fattr] == None: raise AttributeError("missing '%s' attribute for '%s'" % (fattr, self.__class__.__name__)) if fkind in (self.FTYPE_MANDATORY, self.FTYPE_OPTIONAL): # Verify SISField types. if (self.__dict__[fattr] != None and fieldnumtoname[self.__dict__[fattr].fieldtype] != ffmt): raise TypeError( "attribute '%s' for '%s' is of invalid SISField type" % (fattr, self.__class__.__name__)) elif fkind == self.FTYPE_ARRAY: # Verify SISArray contents. if (fieldnumtoname[self.__dict__[fattr].fieldtype] != "SISArray" or fieldnumtoname[self.__dict__[fattr].SISFieldType] != ffmt): raise TypeError( "SISArray attribute '%s' for '%s' is of invalid type" % (fattr, self.__class__.__name__)) def fromstring(self, string): # Parse field header. ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) # Recursively parse subfields. pos = hdrlen reuse = None # SISField to re-use or None try: for fattr, fkind, ffmt in self.subfields: field = None # No value by default if fkind == self.FTYPE_INTEGRAL: # Integer, unpack it. if reuse: # It is an error if there is a field to # re-use present at this time. raise ValueError("integral field preceded optional") n = struct.calcsize(ffmt) field = struct.unpack(ffmt, string[pos:(pos + n)])[0] pos += n else: # SISField, read data from string or # re-use field from previous round. if not reuse: # No old field to handle, convert string to SISField. if pos < (hdrlen + flen): field, n = SISField(string[pos:(hdrlen + flen)], False) pos += n elif fkind != self.FTYPE_OPTIONAL: # No more data in string, raise an exception. raise ValueError("unexpected end-of-data") else: # Field from previous round present, re-use it. field = reuse reuse = None # Verify SISField type. if field != None: fname = fieldnumtoname[field.fieldtype] if fkind == self.FTYPE_ARRAY: if (fname != "SISArray" or fieldnumtoname[field.SISFieldType] != ffmt): # Wrong type of fields inside SISArray, # raise an exception. raise ValueError("invalid SISArray type") elif fkind == self.FTYPE_MANDATORY: if fname != ffmt: # Mandatory field missing, raise an exception. raise ValueError("mandatory field missing") elif fkind == self.FTYPE_OPTIONAL: if fname != ffmt: # Wrong type for optional field. Skip optional # field and re-use already parsed field on next # round. reuse = field field = None # Introduce field as an instance variable. self.__dict__[fattr] = field except (ValueError, KeyError, struct.error): if DEBUG > 0: # DEBUG: Raise a detailed exception. raise else: raise SISException("invalid '%s' structure" % self.__class__.__name__) def tostring(self): # Recursively create strings from subfields. fstrings = [None] totlen = 0 for fattr, fkind, ffmt in self.subfields: field = self.__dict__[fattr] if fkind == self.FTYPE_INTEGRAL: # Integer, pack it. try: string = struct.pack(ffmt, field) except: print "%s %s %s" % (self.__class__, ffmt, repr(field)) raise fstrings.append(string) totlen += len(string) else: if field == None: if fkind == self.FTYPE_OPTIONAL: # Optional field missing, skip it. pass else: # Mandatory field missing, raise an exception. raise SISException("field '%s' missing for '%s'" % (fattr, self.__dict__.__name__)) else: # Convert SISField to string. string = field.tostring() fstrings.append(string) totlen += len(string) try: del string # Try to free some memory early. except: pass fstrings[0] = makesisfieldheader(self.fieldtype, totlen) fstrings.append(makesisfieldpadding(totlen)) # TODO: Heavy on memory, optimize (new string type with concat.) return "".join(fstrings) class SISFieldSpecial(SISFieldBase): '''SISField base class for special fields (fields that do something special for the data they contain or the data is of variable length)''' def __init__(self, **kwds): # Set instance variables. SISFieldBase.__init__(self, **kwds) ############################################################################## # Special SISField subclasses ############################################################################## class SISString(SISFieldSpecial): '''UCS-2 (UTF-16LE) string''' def __init__(self, **kwds): # Set default values. self.String = None # Parse keyword parameters. SISFieldSpecial.__init__(self, **kwds) # Check that all required instance variables are set. if self.String == None: raise AttributeError("missing '%s' attribute for '%s'" % ("String", self.__class__.__name__)) def fromstring(self, string): ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) self.String = string[hdrlen:(hdrlen + flen)].decode("UTF-16LE") def tostring(self): encstr = self.String.encode("UTF-16LE") return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(encstr)), encstr, makesisfieldpadding(len(encstr))) def __str__(self): # Always return Unicode string. Let Python default encoding handle it. return u"" % self.String class SISArray(SISFieldSpecial): '''An array of other SISFields, all of the same type''' def __init__(self, **kwds): # Set default values. self.SISFieldType = None # Invalid type, checked later. self.SISFields = [] # Parse keyword parameters. SISFieldSpecial.__init__(self, **kwds) # Make a copy of the supplied list # (caller may try to modify the original). self.SISFields = self.SISFields[:] # Allow type to be a string or number. self.SISFieldType = fieldnametonum.get(self.SISFieldType, self.SISFieldType) # Get type of first field if not given explicitly. if self.SISFieldType == None: if len(self.SISFields) > 0: self.SISFieldType = self.SISFields[0].fieldtype else: raise AttributeError("no SISFieldType given") # Check that all fields are of the same type. for f in self.SISFields: if f.fieldtype != self.SISFieldType: raise TypeError("SISFieldType mismatch for SISArray") def fromstring(self, string): # Parse field header. ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) if flen < 4: raise SISException("not enough data for a complete SISArray header") # Get array type (type of SISFields in the array). atype = struct.unpack("" % ( len(self.SISFields), fieldnumtoname[self.SISFieldType]) # Standard list semantics ([n:m], len, append, insert, pop, del, iteration) def __getitem__(self, key): # Support older Python versions as well (v2.0 onwards). try: return self.SISFields[key] except TypeError: return self.SISFields[key.start:key.stop] def __setitem__(self, key, value): # Support older Python versions as well (v2.0 onwards). try: self.SISFields[key] = value except TypeError: self.SISFields[key.start:key.stop] = value def __delitem__(self, key): # Support older Python versions as well (v2.0 onwards). try: del self.SISFields[key] except TypeError: del self.SISFields[key.start:key.stop] # Not supported in Python v2.2, where __getitem__() is used instead. # def __iter__(self): # return self.SISFields.__iter__() def __len__(self): return self.SISFields.__len__() def append(self, obj): return self.SISFields.append(obj) def insert(self, idx, obj): return self.SISFields.insert(idx, obj) def extend(self, iterable): return self.SISFields.extend(iterable) def pop(self): return self.SISFields.pop() class SISCompressed(SISFieldSpecial): '''A compression wrapper for another SISField or raw data''' def __init__(self, **kwds): # Set default values. self.CompressionAlgorithm = None self.Data = None if "rawdatainside" in kwds: self.rawdatainside = kwds["rawdatainside"] del kwds["rawdatainside"] else: # Wrap a SISField by default. self.rawdatainside = False # Parse keyword parameters. SISFieldSpecial.__init__(self, **kwds) # Check that all required instance variables are set. if self.CompressionAlgorithm == None or self.Data == None: raise AttributeError("missing '%s' or '%s' attribute for '%s'" % ("CompressionAlgorithm", "Data", self.__class__.__name__)) # Check that the compression algorithm is a known one. if self.CompressionAlgorithm not in (ECompressAuto, ECompressNone, ECompressDeflate): raise TypeError("invalid CompressionAlgorithm '%d'" % self.CompressionAlgorithm) def fromstring(self, string): # Parse field header. ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) if flen < 12: raise SISException("SISCompressed contents too short") compalgo, uncomplen = struct.unpack("= len(string): # Compression is not beneficial, use data as-is. cstring = string compalgo = ECompressNone elif self.CompressionAlgorithm == ECompressNone: # No compression, simply use data as-is. cstring = string compalgo = ECompressNone elif self.CompressionAlgorithm == ECompressDeflate: # Already handled above. pass else: raise SISException("invalid SISCompressed algorithm '%d'" % self.CompressionAlgorithm) # Construct the SISCompressed and SISField headers. chdr = struct.pack("" % (dtype, compalgo) class SISBlob(SISFieldSpecial): '''Arbitrary binary data holder''' def __init__(self, **kwds): # Set default values. self.Data = None # Parse keyword parameters. SISFieldSpecial.__init__(self, **kwds) # Check that all required instance variables are set. if self.Data == None: raise AttributeError("missing '%s' attribute for '%s'" % ("Data", self.__class__.__name__)) def fromstring(self, string): ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) # Does not get any simpler than this. self.Data = string[hdrlen:(hdrlen + flen)] def tostring(self): # TODO: Heavy on memory, optimize (new string type with concat.) return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(self.Data)), self.Data, makesisfieldpadding(len(self.Data))) def __str__(self): return u"" % len(self.Data) class SISFileData(SISFieldSpecial): '''File binary data holder (wraps a special SISCompressed SISField)''' def __init__(self, **kwds): # Create a special SISCompressed object. self.FileData = SISCompressed(CompressionAlgorithm = ECompressNone, Data = "", rawdatainside = True) # Parse keyword parameters. SISFieldSpecial.__init__(self, **kwds) def fromstring(self, string): # Parse field header. ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) if flen < 20: raise SISException("SISFileData contents too short") self.FileData.fromstring(string[hdrlen:(hdrlen + flen)]) def tostring(self): string = self.FileData.tostring() # TODO: Heavy on memory, optimize (new string type with concat.) return "%s%s%s" % (makesisfieldheader(self.fieldtype, len(string)), string, makesisfieldpadding(len(string))) def getcompressedlength(self): # TODO: This is stupid! Compressing the data just # to find the resulting length is not very efficient... string = self.FileData.tostring() ftype, hdrlen, flen, padlen = parsesisfieldheader(string) return (flen - 12) # SISCompressed has an internal header of 12 bytes. def __str__(self): return "" % len(self.FileData.Data) class SISCapabilities(SISFieldSpecial): '''Variable length capability bitmask''' def __init__(self, **kwds): # Set default values. self.Capabilities = None # Parse keyword parameters. SISFieldSpecial.__init__(self, **kwds) # Check that all required instance variables are set. if self.Capabilities == None: raise AttributeError("missing '%s' attribute for '%s'" % ("Capabilities", self.__class__.__name__)) # Check that the bitmask is a multiple of 32 bits. if len(self.Capabilities) & 3 != 0: raise SISException("capabilities length not a multiple of 32 bits") def fromstring(self, string): ftype, hdrlen, flen, padlen = parsesisfieldheader(string, self.fieldtype) caps = string[hdrlen:(hdrlen + flen)] if len(caps) & 3 != 0: raise SISException("capabilities length not a multiple of 32 bits") self.Capabilities = caps def tostring(self): if len(self.Capabilities) & 3 != 0: raise SISException("capabilities length not a multiple of 32 bits") return "%s%s" % (makesisfieldheader(self.fieldtype, len(self.Capabilities)), self.Capabilities) ############################################################################## # Normal SISField subclasses (fields containing only other fields and integers) ############################################################################## class SISVersion(SISFieldNormal): '''Major, minor and build numbers''' def __init__(self, **kwds): self.subfields = [ ("Major", self.FTYPE_INTEGRAL, "" % (self.Major, self.Minor, self.Build) class SISVersionRange(SISFieldNormal): '''A range of two SISVersions, or optionally only one''' def __init__(self, **kwds): self.subfields = [ ("FromVersion", self.FTYPE_MANDATORY, "SISVersion"), ("ToVersion", self.FTYPE_OPTIONAL, "SISVersion")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) def __str__(self): ver1 = "from %d, %d, %d" % (self.FromVersion.Major, self.FromVersion.Minor, self.FromVersion.Build) ver2 = "onwards" if self.ToVersion: ver2 = "to %d, %d, %d" % (self.ToVersion.Major, self.ToVersion.Minor, self.ToVersion.Build) return "" % (ver1, ver2) class SISDate(SISFieldNormal): '''Year, month (0-11) and day (1-31)''' def __init__(self, **kwds): self.subfields = [ ("Year", self.FTYPE_INTEGRAL, "" % (self.Year, self.Month + 1, self.Day) class SISTime(SISFieldNormal): '''Hours (0-23), minutes (0-59) and seconds (0-59)''' def __init__(self, **kwds): self.subfields = [ ("Hours", self.FTYPE_INTEGRAL, "" % ( self.Hours, self.Minutes, self.Seconds) class SISDateTime(SISFieldNormal): '''A bundled SISDate and a SISTime''' def __init__(self, **kwds): self.subfields = [ ("Date", self.FTYPE_MANDATORY, "SISDate"), ("Time", self.FTYPE_MANDATORY, "SISTime")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) def __str__(self): return "" % ( self.Date.Year, self.Date.Month, self.Date.Day, self.Time.Hours, self.Time.Minutes, self.Time.Seconds) class SISUid(SISFieldNormal): '''A 32-bit Symbian OS UID''' def __init__(self, **kwds): self.subfields = [("UID1", self.FTYPE_INTEGRAL, "" % self.UID1 class SISLanguage(SISFieldNormal): '''A Symbian OS language number''' def __init__(self, **kwds): self.subfields = [("Language", self.FTYPE_INTEGRAL, "" % (self.Language, lname) class SISContents(SISFieldNormal): '''The root type of a SIS file''' def __init__(self, **kwds): self.subfields = [ ("ControllerChecksum", self.FTYPE_OPTIONAL, "SISControllerChecksum"), ("DataChecksum", self.FTYPE_OPTIONAL, "SISDataChecksum"), ("Controller", self.FTYPE_MANDATORY, "SISCompressed"), ("Data", self.FTYPE_MANDATORY, "SISData")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) def __str__(self): cksum1 = "N/A" if self.ControllerChecksum: cksum1 = "0x%04x" % self.ControllerChecksum.Checksum cksum2 = "N/A" if self.DataChecksum: cksum2 = "0x%04x" % self.DataChecksum.Checksum return "" % (cksum1, cksum2) class SISController(SISFieldNormal): '''SIS file metadata''' def __init__(self, **kwds): # Convert a list of signatures to separate parameters # so that base class constructor can parse them. # Support upto MAXNUMSIGNATURES signature certificates. if "Signatures" in kwds: signatures = kwds["Signatures"] if len(signatures) > MAXNUMSIGNATURES: raise ValueError("too many signatures for SISController") for n in xrange(len(signatures)): kwds["Signature%d" % n] = signatures[n] del kwds["Signatures"] # DataIndex is really not optional. However, calculating # signatures require that SISController strings without # the DataIndex field can be generated. self.subfields = [ ("Info", self.FTYPE_MANDATORY, "SISInfo"), ("Options", self.FTYPE_MANDATORY, "SISSupportedOptions"), ("Languages", self.FTYPE_MANDATORY, "SISSupportedLanguages"), ("Prerequisites", self.FTYPE_MANDATORY, "SISPrerequisites"), ("Properties", self.FTYPE_MANDATORY, "SISProperties"), ("Logo", self.FTYPE_OPTIONAL, "SISLogo"), ("InstallBlock", self.FTYPE_MANDATORY, "SISInstallBlock"), ("Signature0", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature1", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature2", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature3", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature4", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature5", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature6", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("Signature7", self.FTYPE_OPTIONAL, "SISSignatureCertificateChain"), ("DataIndex", self.FTYPE_OPTIONAL, "SISDataIndex")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) # Helper routines to deal with the special signature fields. def getsignatures(self): # Return signatures as a list. signatures = [] for n in xrange(MAXNUMSIGNATURES): sig = self.__dict__["Signature%d" % n] if sig != None: signatures.append(sig) return signatures def setsignatures(self, signatures): # Replace signatures with the ones from list. If there are # less than MAXNUMSIGNATURES signatures in the list, the # rest are erased. To erase all signatures, call # controller.setsignatures([]). numsig = len(signatures) if numsig > MAXNUMSIGNATURES: raise ValueError("too many signatures for SISController") for n in xrange(MAXNUMSIGNATURES): if n < numsig: sig = signatures[n] else: sig = None self.__dict__["Signature%d" % n] = sig class SISInfo(SISFieldNormal): '''Information about the SIS file''' def __init__(self, **kwds): self.subfields = [ ("UID", self.FTYPE_MANDATORY, "SISUid"), ("VendorUniqueName", self.FTYPE_MANDATORY, "SISString"), ("Names", self.FTYPE_ARRAY, "SISString"), ("VendorNames", self.FTYPE_ARRAY, "SISString"), ("Version", self.FTYPE_MANDATORY, "SISVersion"), ("CreationTime", self.FTYPE_MANDATORY, "SISDateTime"), ("InstallType", self.FTYPE_INTEGRAL, "" % self.Checksum class SISDataChecksum(SISFieldNormal): '''CCITT CRC-16 of the SISData SISField''' def __init__(self, **kwds): self.subfields = [("Checksum", self.FTYPE_INTEGRAL, "" % self.Checksum class SISSignature(SISFieldNormal): '''Cryptographic signature of preceding SIS metadata''' def __init__(self, **kwds): self.subfields = [ ("SignatureAlgorithm", self.FTYPE_MANDATORY, "SISSignatureAlgorithm"), ("SignatureData", self.FTYPE_MANDATORY, "SISBlob")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) class SISSignatureAlgorithm(SISFieldNormal): '''Object identifier string of a signature algorithm''' def __init__(self, **kwds): self.subfields = [ ("AlgorithmIdentifier", self.FTYPE_MANDATORY, "SISString")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) def __str__(self): return "" % ( self.AlgorithmIdentifier.String) class SISSignatureCertificateChain(SISFieldNormal): '''An array of SISSignatures and a SIScertificateChain for signature validation''' def __init__(self, **kwds): self.subfields = [ ("Signatures", self.FTYPE_ARRAY, "SISSignature"), ("CertificateChain", self.FTYPE_MANDATORY, "SISCertificateChain")] # Parse keyword parameters. SISFieldNormal.__init__(self, **kwds) class SISDataIndex(SISFieldNormal): '''Data index for files belonging to a SISController''' def __init__(self, **kwds): self.subfields = [("DataIndex", self.FTYPE_INTEGRAL, "" % self.DataIndex ############################################################################## # Utility dictionaries ############################################################################## fieldinfo = [ (1, "SISString", SISString), (2, "SISArray", SISArray), (3, "SISCompressed", SISCompressed), (4, "SISVersion", SISVersion), (5, "SISVersionRange", SISVersionRange), (6, "SISDate", SISDate), (7, "SISTime", SISTime), (8, "SISDateTime", SISDateTime), (9, "SISUid", SISUid), (11, "SISLanguage", SISLanguage), (12, "SISContents", SISContents), (13, "SISController", SISController), (14, "SISInfo", SISInfo), (15, "SISSupportedLanguages", SISSupportedLanguages), (16, "SISSupportedOptions", SISSupportedOptions), (17, "SISPrerequisites", SISPrerequisites), (18, "SISDependency", SISDependency), (19, "SISProperties", SISProperties), (20, "SISProperty", SISProperty), # SISSignatures: Legacy field type, not used # (21, "SISSignatures", SISSignatures), (22, "SISCertificateChain", SISCertificateChain), (23, "SISLogo", SISLogo), (24, "SISFileDescription", SISFileDescription), (25, "SISHash", SISHash), (26, "SISIf", SISIf), (27, "SISElseIf", SISElseIf), (28, "SISInstallBlock", SISInstallBlock), (29, "SISExpression", SISExpression), (30, "SISData", SISData), (31, "SISDataUnit", SISDataUnit), (32, "SISFileData", SISFileData), (33, "SISSupportedOption", SISSupportedOption), (34, "SISControllerChecksum", SISControllerChecksum), (35, "SISDataChecksum", SISDataChecksum), (36, "SISSignature", SISSignature), (37, "SISBlob", SISBlob), (38, "SISSignatureAlgorithm", SISSignatureAlgorithm), (39, "SISSignatureCertificateChain", SISSignatureCertificateChain), (40, "SISDataIndex", SISDataIndex), (41, "SISCapabilities", SISCapabilities) ] fieldnumtoclass = dict([(num, klass) for num, name, klass in fieldinfo]) fieldnametonum = dict([(name, num) for num, name, klass in fieldinfo]) fieldnumtoname = dict([(num, name) for num, name, klass in fieldinfo]) ensymble-0.29/ensymble/utils/rscfile.py0000664000076400007640000001550411373531772017327 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # rscfile.py - Symbian OS compiled resource file (RSC) utilities # Copyright 2006, 2007 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import struct import symbianutil ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def makeuidfromoffset(offset): '''Convert a Symbian OS resource file offset to a UID. ---- From rcomp v7.01 source ---- space: 0, A: 1, B: 2, ..., Z: 26 ABCD corresponds to the number 4321 which becomes ((4*27 + 3) * 27 + 2) * 27 + 1. ---- ---- The description above contains an error. The actual situation is reversed: ABCD corresponds to the number 1234 which results in ((1*27 + 2) * 27 + 3) * 27 + 4. ''' if len(offset) not in range(1, 5): raise ValueError("offset must be four characters or less") uid = 0L offset = offset.upper() for c in offset: try: ordc = " ABCDEFGHIJKLMNOPQRSTUVWXYZ".index(c) except ValueError: raise ValueError("invalid character '%s' in offset" % c) uid *= 27 uid += ordc return uid ############################################################################## # Resource class for containing binary fields ############################################################################## class Resource(object): '''A Symbian OS resource type Limitations: - Write only - Available types are limited to BYTE, WORD, LONG, LLINK and LTEXT. - Unicode compression is not supported.''' def __init__(self, fieldtypes, *args): self.fieldtypes = fieldtypes self.fieldvalues = [] if len(self.fieldtypes) != len(args): raise ValueError("invalid number of field values") offset = 0 for n in xrange(len(args)): ftype = self.fieldtypes[n] fval = args[n] if ftype == "BYTE": if fval < 0: fval += 0x100 if fval < 0 or fval > 255: raise ValueError("byte integer too large") self.fieldvalues.append(struct.pack(" 65535: raise ValueError("word integer too large") self.fieldvalues.append(struct.pack(" 4294967295: raise ValueError("long integer too large") self.fieldvalues.append(struct.pack(" 255: raise ValueError("Unicode string too long") self.fieldvalues.append(struct.pack(" 0: if (offset & 1) != 0: # Odd offset. Add padding byte (only if length > 0). self.fieldvalues.append(struct.pack("B", 0xab)) offset += 1 fval_enc = fval.encode("UTF-16LE") self.fieldvalues.append(fval_enc) offset += len(fval_enc) # TODO: Arrays, recursive structs # TODO: TEXT, DOUBLE, BUF, BUF8, BUF, LINK, SRLINK def tostring(self): return "".join(self.fieldvalues) # TODO: fromstring() ############################################################################## # RSCWriter class for creating Symbian OS compiled resource files (RSC) ############################################################################## class RSCWriter(object): '''A Symbian OS compiled resource file (RSC) file generator Limitations: - Output format is always "Compressed Unicode resource format". - Despite the format name, nothing is compressed.''' def __init__(self, uid2, uid3 = None, offset = None): self.resources = [] if ((uid3 == None and offset == None) or (uid3 != None and offset != None)): raise AttributeError("one of uid3 or offset required, not both") self.flags = 0x00 if offset != None: try: uid3 = makeuidfromoffset(offset) except: raise ValueError("invalid offset '%s'" % offset) self.flags = 0x01 self.uid2 = uid2 self.uid3 = uid3 def addresource(self, resource): self.addrawresource(resource.tostring()) def addrawresource(self, string): self.resources.append(string) def tostring(self): # UIDs (UID1 always 0x101f4a6b) fields = [symbianutil.uidstostring(0x101f4a6bL, self.uid2, self.uid3)] # Flags fields.append(struct.pack(" maxrlen: maxrlen = rlen offsets.append(foffset) fields.append(res) foffset += rlen offsets.append(foffset) # Update longest resource. fields[2] = struct.pack(" None active Debug output enabled / disabled, a boolean value Debug output consists of OpenSSL binary command line and any output produced to the standard error stream by OpenSSL. ''' global openssldebug openssldebug = not not active # Convert to boolean. def signstring(privkey, passphrase, string): ''' Sign a binary string using a given private key and its pass phrase. signstring(...) -> (signature, keytype) privkey RSA or DSA private key, a string in PEM (base-64) format passphrase pass phrase for the private key, a non-Unicode string or None string a binary string to sign signature signature, an ASN.1 encoded binary string keytype detected key type, string, "RSA" or "DSA" NOTE: On platforms with poor file system security, decrypted version of the private key may be grabbed from the temporary directory! ''' if passphrase == None or len(passphrase) == 0: # OpenSSL does not like empty stdin while reading a passphrase from it. passphrase = "\n" # Create a temporary directory for OpenSSL to work in. tempdir = mkdtemp("ensymble-XXXXXX") keyfilename = os.path.join(tempdir, "privkey.pem") sigfilename = os.path.join(tempdir, "signature.dat") stringfilename = os.path.join(tempdir, "string.dat") try: # If the private key is in PKCS#8 format, it needs to be converted. privkey = convertpkcs8key(tempdir, privkey, passphrase) # Decrypt the private key. Older versions of OpenSSL do not # accept the "-passin" parameter for the "dgst" command. privkey, keytype = decryptkey(tempdir, privkey, passphrase) if keytype == "DSA": signcmd = "-dss1" elif keytype == "RSA": signcmd = "-sha1" else: raise ValueError("unknown private key type %s" % keytype) # Write decrypted PEM format private key to file. keyfile = file(keyfilename, "wb") keyfile.write(privkey) keyfile.close() # Write binary string to a file. On some systems, stdin is # always in text mode and thus unsuitable for binary data. stringfile = file(stringfilename, "wb") stringfile.write(string) stringfile.close() # Sign binary string using the decrypted private key. command = ("dgst %s -binary -sign %s " "-out %s %s") % (signcmd, quote(keyfilename), quote(sigfilename), quote(stringfilename)) runopenssl(command) signature = "" if os.path.isfile(sigfilename): # Read signature from file. sigfile = file(sigfilename, "rb") signature = sigfile.read() sigfile.close() if signature.strip() == "": # OpenSSL did not create output, something went wrong. raise ValueError("unspecified error during signing") finally: # Delete temporary files. for fname in (keyfilename, sigfilename, stringfilename): try: os.remove(fname) except OSError: pass # Remove temporary directory. os.rmdir(tempdir) return (signature, keytype) def certtobinary(pemcert): ''' Convert X.509 certificates from PEM (base-64) format to DER (binary). certtobinary(...) -> dercert pemcert One or more X.509 certificates in PEM (base-64) format, a string dercert X.509 certificate(s), an ASN.1 encoded binary string ''' # Find base-64 encoded data between header and footer. header = "-----BEGIN CERTIFICATE-----" footer = "-----END CERTIFICATE-----" endoffset = 0 certs = [] while True: # First find a header. startoffset = pemcert.find(header, endoffset) if startoffset < 0: # No header found, stop search. break startoffset += len(header) # Next find a footer. endoffset = pemcert.find(footer, startoffset) if endoffset < 0: # No footer found. raise ValueError("missing PEM certificate footer") # Extract the base-64 encoded certificate and decode it. try: cert = pemcert[startoffset:endoffset].decode("base-64") except: # Base-64 decoding error. raise ValueError("invalid PEM format certificate") certs.append(cert) endoffset += len(footer) if len(certs) == 0: raise ValueError("not a PEM format certificate") # DER certificates are simply raw binary versions # of the base-64 encoded PEM certificates. return "".join(certs) ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def convertpkcs8key(tempdir, privkey, passphrase): ''' Convert a PKCS#8-format RSA or DSA private key to an older SSLeay-compatible format. convertpkcs8key(...) -> privkeyout tempdir Path to pre-existing temporary directory with read/write access privkey RSA or DSA private key, a string in PEM (base-64) format passphrase pass phrase for the private key, a non-Unicode string or None privkeyout decrypted private key in PEM (base-64) format ''' # Determine PKCS#8 private key type. if privkey.find("-----BEGIN PRIVATE KEY-----") >= 0: # Unencrypted PKCS#8 private key encryptcmd = "-nocrypt" elif privkey.find("-----BEGIN ENCRYPTED PRIVATE KEY-----") >= 0: # Encrypted PKCS#8 private key encryptcmd = "" else: # Not a PKCS#8 private key, nothing to do. return privkey keyinfilename = os.path.join(tempdir, "keyin.pem") keyoutfilename = os.path.join(tempdir, "keyout.pem") try: # Write PEM format private key to file. keyinfile = file(keyinfilename, "wb") keyinfile.write(privkey) keyinfile.close() # Convert a PKCS#8 private key to older SSLeay-compatible format. # Keep pass phrase as-is. runopenssl("pkcs8 -in %s -out %s -passin stdin -passout stdin %s" % (quote(keyinfilename), quote(keyoutfilename), encryptcmd), "%s\n%s\n" % (passphrase, passphrase)) privkey = "" if os.path.isfile(keyoutfilename): # Read converted private key back. keyoutfile = file(keyoutfilename, "rb") privkey = keyoutfile.read() keyoutfile.close() if privkey.strip() == "": # OpenSSL did not create output. Probably a wrong pass phrase. raise ValueError("wrong pass phrase or invalid PKCS#8 private key") finally: # Delete temporary files. for fname in (keyinfilename, keyoutfilename): try: os.remove(fname) except OSError: pass return privkey def decryptkey(tempdir, privkey, passphrase): ''' decryptkey(...) -> (privkeyout, keytype) tempdir Path to pre-existing temporary directory with read/write access privkey RSA or DSA private key, a string in PEM (base-64) format passphrase pass phrase for the private key, a non-Unicode string or None string a binary string to sign keytype detected key type, string, "RSA" or "DSA" privkeyout decrypted private key in PEM (base-64) format NOTE: On platforms with poor file system security, decrypted version of the private key may be grabbed from the temporary directory! ''' # Determine private key type. if privkey.find("-----BEGIN DSA PRIVATE KEY-----") >= 0: keytype = "DSA" convcmd = "dsa" elif privkey.find("-----BEGIN RSA PRIVATE KEY-----") >= 0: keytype = "RSA" convcmd = "rsa" else: raise ValueError("not an RSA or DSA private key in PEM format") keyinfilename = os.path.join(tempdir, "keyin.pem") keyoutfilename = os.path.join(tempdir, "keyout.pem") try: # Write PEM format private key to file. keyinfile = file(keyinfilename, "wb") keyinfile.write(privkey) keyinfile.close() # Decrypt the private key. Older versions of OpenSSL do not # accept the "-passin" parameter for the "dgst" command. runopenssl("%s -in %s -out %s -passin stdin" % (convcmd, quote(keyinfilename), quote(keyoutfilename)), passphrase) privkey = "" if os.path.isfile(keyoutfilename): # Read decrypted private key back. keyoutfile = file(keyoutfilename, "rb") privkey = keyoutfile.read() keyoutfile.close() if privkey.strip() == "": # OpenSSL did not create output. Probably a wrong pass phrase. raise ValueError("wrong pass phrase or invalid private key") finally: # Delete temporary files. for fname in (keyinfilename, keyoutfilename): try: os.remove(fname) except OSError: pass return (privkey, keytype) def mkdtemp(template): ''' Create a unique temporary directory. tempfile.mkdtemp() was introduced in Python v2.3. This is for backward compatibility. ''' # Cross-platform way to determine a suitable location for temporary files. systemp = tempfile.gettempdir() if not template.endswith("XXXXXX"): raise ValueError("invalid template for mkdtemp(): %s" % template) for n in xrange(10000): randchars = [] for m in xrange(6): randchars.append(random.choice("abcdefghijklmnopqrstuvwxyz")) tempdir = os.path.join(systemp, template[: -6]) + "".join(randchars) try: os.mkdir(tempdir, 0700) return tempdir except OSError: pass else: # All unique names in use, raise an error. raise OSError(errno.EEXIST, os.strerror(errno.EEXIST), os.path.join(systemp, template)) def quote(filename): '''Quote a filename if it has spaces in it.''' if " " in filename: filename = '"%s"' % filename return filename def runopenssl(command, datain = ""): '''Run the OpenSSL command line tool with the given parameters and data.''' global opensslcommand if opensslcommand == None: # Find path to the OpenSSL command. findopenssl() # Construct a command line for subprocess.Popen() cmdline = (opensslcommand, command) if openssldebug: # Print command line. print "DEBUG: Popen(%s)" % repr(cmdline) # Run command. p = subprocess.Popen(cmdline, stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.PIPE, close_fds=True) (pipein, pipeout, pipeerr) = (p.stdin, p.stdout, p.stderr) pipein.write(datain) dataout = pipeout.read() errout = pipeerr.read() if openssldebug: # Print standard error output. print "DEBUG: pipeerr.read() = %s" % repr(errout) return (dataout, errout) def findopenssl(): '''Find the OpenSSL command line tool.''' global opensslcommand # Get PATH and split it to a list of paths. paths = os.environ["PATH"].split(os.pathsep) # Insert script path in front of others. # On Windows, this is where openssl.exe resides by default. if sys.path[0] != "": paths.insert(0, sys.path[0]) for path in paths: cmd = os.path.join(path, "openssl") try: # Try to query OpenSSL version. p = subprocess.Popen((cmd, 'version'), stdin=subprocess.PIPE, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, close_fds=True) pin, pout = p.stdin, p.stdout verstr = pout.read() except OSError, e: # Could not run command, skip to the next path candidate. continue if verstr.split()[0] == "OpenSSL": # Command found, stop searching. break else: raise IOError("no valid OpenSSL command line tool found in PATH") # Add quotes around command in case of embedded whitespace on path. opensslcommand = quote(cmd) ensymble-0.29/ensymble/utils/sisfile.py0000664000076400007640000006524211373531772017342 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- ############################################################################## # sisfile.py - Symbian OS v9.x SIS file utilities # Copyright 2006, 2007 Jussi Ylänen # # This file is part of Ensymble developer utilities for Symbian OS(TM). # # Ensymble is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # Ensymble is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with Ensymble; if not, write to the Free Software # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ############################################################################## import os import time import struct from hashlib import sha1 import symbianutil import cryptutil import sisfield ############################################################################## # Public module-level functions ############################################################################## def parseexpression(expr): '''Create a SISExpression SISField out of the given expression string. parseexpression(...) -> SISExpression expr the expression, a string SISExpression the returned SISExpression SISField NOTE: Only expressions of form "language == nn" are supported, for now.''' elist = expr.lower().split() # TODO: Only "language == nn" expressions supported for now. # Going to need a real parser for general expression support, though. try: if len(elist) != 3 or elist[0] != 'language' or elist[1] != '==': raise ValueError langnum = int(elist[2]) except ValueError: raise ValueError("invalid expression '%s'" % expr) # Create a SISExpression SISField of type EPrimTypeVariable. leftfield = sisfield.SISExpression(Operator = sisfield.EPrimTypeVariable, IntegerValue = sisfield.EVarLanguage) # Create a SISExpression SISField of type EPrimTypeNumber. rightfield = sisfield.SISExpression(Operator = sisfield.EPrimTypeNumber, IntegerValue = langnum) # Create an equality test SISExpression SISField and return it. return sisfield.SISExpression(Operator = sisfield.EBinOpEqual, IntegerValue = 0, LeftExpression = leftfield, RightExpression = rightfield) def signstring(privkey, passphrase, string): '''Sign a binary string using a given private key and its pass phrase. signstring(...) -> (signature, algorithm oid) privkey private key (RSA or DSA), a binary string in PEM format passphrase pass phrase (non-Unicode) for the private key or None string binary string from which the signature is to be calculated signature signature, a binary string algorithm oid signature algorithm object identifier, a string''' # Sign string. signature, keytype = cryptutil.signstring(privkey, passphrase, string) # Determine algorithm object identifier. if keytype == "DSA": algoid = "1.2.840.10040.4.3" elif keytype == "RSA": algoid = "1.2.840.113549.1.1.5" else: raise ValueError("unknown key type '%s'" % keytype) return (signature, algoid) ############################################################################## # Module-level functions which are normally only used by this module ############################################################################## def makefiledata(contents): '''Make a SISFileData SISField out of the given binary string. makefiledata(...) -> SISFileData contents file contents, a binary string SISFileData the returned SISFileData instance NOTE: Data is compressed only if it is beneficial.''' # Wrap data inside SISCompressed SISField. cfield = sisfield.SISCompressed(Data = contents, CompressionAlgorithm = sisfield.ECompressAuto, rawdatainside = True) # Create a SISFileData SISField out of the wrapped data and return it. return sisfield.SISFileData(FileData = cfield) def makefiledesc(contents, compressedlen, index, target = None, mimetype = None, capabilities = None, operation = sisfield.EOpInstall, options = 0): '''Make a SISFileDescription SISField for the given file. makefiledesc(...) -> SISFileDescription contents file contents for SHA-1 digest calc., a binary string compressedlen length of file contents inside a SISCompressed SISField index index of file inside a SISDataUnit SISField, an integer target install path in target device, a string or None mimetype MIME type, a string or None capabilities Symbian OS capabilities for EXE-files, int. mask or None operation what to do with the file, an integer bit mask options operation dependent install options, an integer bit mask SISFileDescription the returned SISFileDescription instance Constants for operation and options can be found in the sisfield module. Operation is one of EOpInstall, EOpRun, EOpText or EOpNull. Options depend on the selected operation, for example EInstVerifyOnRestore.''' # Create a SISString of target path. if target == None: # Target may be None. The file is not installed anywhere in that case. target = "" targetfield = sisfield.SISString(String = target) # Create a SISString of MIME type. if mimetype == None: # MIME type may be None (and usually is). mimetype = "" mimetypefield = sisfield.SISString(String = mimetype) # Create a SISCapabilities SISField for executable capabilities. if capabilities != None and capabilities != 0L: # SISCapabilities expects a binary string, need to convert the # capability mask. If capability mask is 0, no capability field # is generated. Otherwise signsis.exe cannot sign the resulting # SIS file. capstring = symbianutil.capmasktorawdata(capabilities) capfield = sisfield.SISCapabilities(Capabilities = capstring) else: # Only EXE- and DLL-files have a concept of capability. capfield = None # Calculate file hash using SHA-1. Create a SISHash SISField out of it. # Contents may be None, to properly support the EOpNull install operation. if contents != None: sha1hash = sha1(contents).digest() else: # No data, the containing SISBlob is mandatory but empty. sha1hash = "" hashblob = sisfield.SISBlob(Data = sha1hash) hashfield = sisfield.SISHash(HashAlgorithm = sisfield.ESISHashAlgSHA1, HashData = hashblob) # Create a SISFileDescription SISField and return it. return sisfield.SISFileDescription(Target = targetfield, MIMEType = mimetypefield, Capabilities = capfield, Hash = hashfield, Operation = operation, OperationOptions = options, Length = compressedlen, UncompressedLength = len(contents), FileIndex = index) def makedependency(uid, fromversion, toversion, names): '''Make a SISDependency SISField for the given UID, version dependency. makedependency(...) -> SISDependency uid UID, an unsigned integer fromversion from-version, a triple-item list/tuple (major, minor, build) toversion to-version, a triple-item list/tuple or None names names for the dependency, a list of string per language SISDependency the returned SISDependency SISField NOTE: toversion may be None, indicating any version after fromversion.''' # Convert parameters to SISFields. uidfield = sisfield.SISUid(UID1 = uid) fromverfield = sisfield.SISVersion(Major = fromversion[0], Minor = fromversion[1], Build = fromversion[2]) if toversion != None: toverfield = sisfield.SISVersion(Major = toversion[0], Minor = toversion[1], Build = toversion[2]) else: toverfield = None verrangefield = sisfield.SISVersionRange(FromVersion = fromverfield, ToVersion = toverfield) l = [] for name in names: l.append(sisfield.SISString(String = name)) namesfield = sisfield.SISArray(SISFields = l, SISFieldType = "SISString") # Create a SISDependency SISField and return it. return sisfield.SISDependency(UID = uidfield, VersionRange = verrangefield, DependencyNames = namesfield) def makeinstallblock(files = [], embeddedsisfiles = [], ifblocks = []): '''Make a SISInstallBlock SISField out of the given lists of SISFields. makeinstallblock(...) -> SISInstallBlock files a list of SISFileDescription SISFields (normal files) embeddedsisfiles a list of SISController SISFields (embedded SIS files) ifblocks a list of SISIf SISFields (conditionally installed files) SISInstallBlock the returned SISInstallBlock instance NOTE: Any of the lists may be empty (and are, by default).''' # Convert lists to SISArrays. sa1 = sisfield.SISArray(SISFields = files, SISFieldType = "SISFileDescription") sa2 = sisfield.SISArray(SISFields = embeddedsisfiles, SISFieldType = "SISController") sa3 = sisfield.SISArray(SISFields = ifblocks, SISFieldType = "SISIf") # Create a SISInstallBlock SISField and return it. return sisfield.SISInstallBlock(Files = sa1, EmbeddedSISFiles = sa2, IfBlocks = sa3) def makelangconditional(languages, langdepfiles): '''Make a SISIf and SISElseIfs for language dependent installation of files. makelangconditional(...) -> SISIf or None languages a list of language numbers (not names, IDs or SISLanguages) landepfiles a list of file lists, where each file list is a list of alternative SISFileDescription SISFields for each language SISIf the returned SISIf instance or None if no files''' if len(langdepfiles) == 0: # No language dependent files, leave. return None # Create a file list per language. filesperlang = [] for n in xrange(len(languages)): filesperlang.append([]) # Gather all files from the same language to a single list. for files in langdepfiles: if len(files) != len(languages): raise ValueError("%d files given but number of languages is %d" % (len(files), len(languages))) for n in xrange(len(languages)): filesperlang[n].append(files[n]) if len(languages) == 0: # No languages, leave. (This is down here so that errors # can still be caught above.) return None # Create a SISArray of SISElseIf SISFields. elseiffields = [] for n in xrange(1, len(languages)): elseifexpfield = parseexpression("language == %d" % languages[n]) elseiffield = sisfield.SISElseIf(Expression = elseifexpfield, InstallBlock = makeinstallblock(filesperlang[n])) elseiffields.append(elseiffield) elseiffieldarray = sisfield.SISArray(SISFields = elseiffields, SISFieldType = "SISElseIf") # Create and return the final SISIf SISField. ifexpfield = parseexpression("language == %d" % languages[0]) return sisfield.SISIf(Expression = ifexpfield, InstallBlock = makeinstallblock(filesperlang[0]), ElseIfs = elseiffieldarray) ############################################################################## # SimpleSISWriter class for no-frills SIS file generation ############################################################################## class SimpleSISWriter(object): '''A no-frills SIS file generator Limitations: - Option lists are not supported. - Condition blocks are not supported. Languages are, however. - Nested SIS files are not supported. - SIS type is always a full installation package (type EInstInstallation). - Package options (EInstFlagShutdownApps) are not supported.''' def __init__(self, languages, names, uid, version, vendorname, vendornames, creationtime = None): # Set empty list of languages, names, files, certificates and so on. self.languages = [] self.filedata = [] self.files = [] self.langdepfiles = [] self.logo = None self.certificates = [] self.targetdevices = [] self.dependencies = [] self.properties = [] # Convert language IDs/names to language numbers. for lang in languages: try: langnum = symbianutil.langidtonum[lang] except KeyError: # Not a language ID, try names next. try: langnum = symbianutil.langnametonum[lang] except KeyError: raise ValueError("invalid language '%s'" % lang) self.languages.append(langnum) # Verify number of names and vendor names wrt. number of languages. if len(names) != len(self.languages): raise ValueError( "%d package names given but number of languages is %d" % (len(names), len(self.languages))) if len(vendornames) != len(self.languages): raise ValueError( "%d vendor names given but number of languages is %d" % (len(vendornames), len(self.languages))) # Convert language dependent names to a SISArray of SISStrings. l = [] for name in names: l.append(sisfield.SISString(String = name)) self.names = sisfield.SISArray(SISFields = l, SISFieldType = "SISString") # Convert integer UID to SISUid SISField. self.uid = sisfield.SISUid(UID1 = uid) # Convert version number triplet to SISVersion SISField. self.version = sisfield.SISVersion(Major = version[0], Minor = version[1], Build = version[2]) # Convert unique vendor name to SISString SISField. self.vendorname = sisfield.SISString(String = vendorname) # Convert language dependent vendor names to a SISArray of SISStrings. l = [] for name in vendornames: l.append(sisfield.SISString(String = name)) self.vendornames = sisfield.SISArray(SISFields = l, SISFieldType = "SISString") if creationtime == None: # If no creation time given, use the time # of SimpleSISWriter instantiation. creationtime = time.gmtime() # Convert standard Python time representation to SISFields. datefield = sisfield.SISDate(Year = creationtime.tm_year, Month = creationtime.tm_mon - 1, Day = creationtime.tm_mday) timefield = sisfield.SISTime(Hours = creationtime.tm_hour, Minutes = creationtime.tm_min, Seconds = creationtime.tm_sec) self.creationtime = sisfield.SISDateTime(Date = datefield, Time = timefield) def setlogo(self, contents, mimetype): '''Add a logo graphics to generated SIS file. NOTE: Not all devices display a logo during installation.''' if self.logo != None: raise ValueError("logo already set") # Create SISFileData and SISFileDescription SISFields. filedata = makefiledata(contents) complen = filedata.getcompressedlength() runopts = (sisfield.EInstFileRunOptionInstall | sisfield.EInstFileRunOptionByMimeType) filedesc = makefiledesc(contents, complen, len(self.filedata), None, mimetype, None, sisfield.EOpRun, runopts) self.logo = sisfield.SISLogo(LogoFile = filedesc) self.filedata.append(filedata) def addfile(self, contents, target = None, mimetype = None, capabilities = None, operation = sisfield.EOpInstall, options = 0): '''Add a file that is same for all languages to generated SIS file.''' # Create SISFileData and SISFileDescription SISFields. filedata = makefiledata(contents) complen = filedata.getcompressedlength() metadata = makefiledesc(contents, complen, len(self.filedata), target, mimetype, capabilities, operation, options) self.files.append(metadata) self.filedata.append(filedata) def addlangdepfile(self, clist, target = None, mimetype = None, capabilities = None, operation = sisfield.EOpInstall, options = 0): '''Add language dependent files to generated SIS file. A conditional expression is automatically generated for the file.''' if len(clist) != len(self.languages): raise ValueError("%d files given but number of languages is %d" % (len(clist), len(self.languages))) data = [] files = [] index = len(self.filedata) for contents in clist: # Create SISFileData and SISFileDescription SISFields. filedata = makefiledata(contents) complen = filedata.getcompressedlength() metadata = makefiledesc(contents, complen, index, target, mimetype, capabilities, operation, options) files.append(metadata) data.append(filedata) index += 1 self.langdepfiles.append(files) self.filedata.extend(data) def addcertificate(self, privkey, cert, passphrase): '''Add a certificate to SIS file. Private key and certificate are in PEM (base-64) format.''' self.certificates.append((privkey, cert, passphrase)) def addtargetdevice(self, uid, fromversion, toversion, names): '''Add a mandatory target device UID to generated SIS file. NOTE: Names are not usually displayed. Instead, the device vendor has specified what the names must be.''' if len(names) != len(self.languages): raise ValueError( "%d device names given but number of languages is %d" % (len(names), len(self.languages))) depfield = makedependency(uid, fromversion, toversion, names) self.targetdevices.append(depfield) def adddependency(self, uid, fromversion, toversion, names): '''Add an installed package dependency to generated SIS file. NOTE: Some devices display the first name of the dependency regardless of the device language.''' if len(names) != len(self.languages): raise ValueError( "%d dependency names given but number of languages is %d" % (len(names), len(self.languages))) depfield = makedependency(uid, fromversion, toversion, names) self.dependencies.append(depfield) def addproperty(self, key, value): '''Add a property key, value pair to generated SIS file. When installing other SIS files, they may query these properties.''' # Convert parameters to a SISProperty SISField. self.properties.append(sisfield.SISProperty(Key = key, Value = value)) def tostring(self): '''Convert this SIS instance to a (possibly very large) string.''' # Generate a SISInfo SISField. infofield = sisfield.SISInfo(UID = self.uid, VendorUniqueName = self.vendorname, Names = self.names, VendorNames = self.vendornames, Version = self.version, CreationTime = self.creationtime, InstallType = sisfield.EInstInstallation, InstallFlags = 0) # Generate an empty SISSupportedOptions SISField. # Option lists are not supported by SimpleSISWriter. sa = sisfield.SISArray(SISFields = [], SISFieldType = "SISSupportedOption") optfield = sisfield.SISSupportedOptions(Options = sa) # Convert language numbers to SISArray of SISLanguages # and generate a SISSupportedLanguages SISField. langfieldlist = [] for lang in self.languages: langfieldlist.append(sisfield.SISLanguage(Language = lang)) sa = sisfield.SISArray(SISFields = langfieldlist, SISFieldType = "SISLanguage") langfield = sisfield.SISSupportedLanguages(Languages = sa) # Generate SISPrerequisites SISField. sa1 = sisfield.SISArray(SISFields = self.targetdevices, SISFieldType = "SISDependency") sa2 = sisfield.SISArray(SISFields = self.dependencies, SISFieldType = "SISDependency") prereqfield = sisfield.SISPrerequisites(TargetDevices = sa1, Dependencies = sa2) # Generate SISProperties SISField. sa = sisfield.SISArray(SISFields = self.properties, SISFieldType = "SISProperty") propfield = sisfield.SISProperties(Properties = sa) # Generate SISInstallBlock SISField. iffield = makelangconditional(self.languages, self.langdepfiles) if iffield: # Some language dependent files iffieldlist = [iffield] else: # No language dependent files iffieldlist = [] ibfield = makeinstallblock(self.files, [], iffieldlist) # Generate a data index field. No embedded SIS files, index is 0. didxfield = sisfield.SISDataIndex(DataIndex = 0) # Generate a SISController SISField without any signatures. ctrlfield = sisfield.SISController(Info = infofield, Options = optfield, Languages = langfield, Prerequisites = prereqfield, Properties = propfield, Logo = self.logo, InstallBlock = ibfield) # Calculate metadata signature for each certificate. certfieldlist = [] for cert in self.certificates: # Calculate a signature of the SISController so far. string = ctrlfield.tostring() string = sisfield.stripheaderandpadding(string) signature, algoid = signstring(cert[0], cert[2], string) # Create a SISCertificateChain SISField from certificate data. sf1 = sisfield.SISBlob(Data = cryptutil.certtobinary(cert[1])) sf2 = sisfield.SISCertificateChain(CertificateData = sf1) # Create a SISSignature SISField from calculated signature. sf3 = sisfield.SISString(String = algoid) sf4 = sisfield.SISSignatureAlgorithm(AlgorithmIdentifier = sf3) sf5 = sisfield.SISBlob(Data = signature) sf6 = sisfield.SISSignature(SignatureAlgorithm = sf4, SignatureData = sf5) # Create a new SISSignatureCertificateChain SISField. sa = sisfield.SISArray(SISFields = [sf6]) certfieldlist.append(sisfield.SISSignatureCertificateChain( Signatures = sa, CertificateChain = sf2)) # Add certificate to SISController SISField. ctrlfield.setsignatures(certfieldlist) # Finally add a data index field to SISController SISField. # and wrap it in SISCompressed SISField. ctrlfield.DataIndex = didxfield ctrlcompfield = sisfield.SISCompressed(Data = ctrlfield, CompressionAlgorithm = sisfield.ECompressDeflate) # Generate SISData SISField. sa = sisfield.SISArray(SISFields = self.filedata, SISFieldType = "SISFileData") dufield = sisfield.SISDataUnit(FileData = sa) sa = sisfield.SISArray(SISFields = [dufield]) datafield = sisfield.SISData(DataUnits = sa) # Calculate SISController checksum. # TODO: Requires an extra tostring() conversion. ctrlcs = symbianutil.crc16ccitt(ctrlcompfield.tostring()) ctrlcsfield = sisfield.SISControllerChecksum(Checksum = ctrlcs) # Calculate SISData checksum. # TODO: Requires an extra tostring() conversion. datacs = symbianutil.crc16ccitt(datafield.tostring()) datacsfield = sisfield.SISDataChecksum(Checksum = datacs) # Generate SISContents SISField. contentsfield = sisfield.SISContents(ControllerChecksum = ctrlcsfield, DataChecksum = datacsfield, Controller = ctrlcompfield, Data = datafield) # Generate a SIS UID string. uidstring = symbianutil.uidstostring(0x10201a7aL, 0x00000000L, self.uid.UID1) # Return the completed SIS file as a string. return uidstring + contentsfield.tostring() def tofile(self, outfile): '''Write this SIS instance to a file object or a named file.''' s = self.tostring() try: f = file(outfile, "wb") try: f.write(s) finally: f.close() except TypeError: f.write(s) ensymble-0.29/setup.py0000664000076400007640000000154411374343415014055 0ustar stevesteve#!/usr/bin/env python # -*- coding: utf-8 -*- from distutils.core import setup import ensymble name = "ensymble" version = ensymble.__version__ license = ensymble.__license__ author = ensymble.__author__ maintainer = ensymble.__maintainer__ maintainer_email = ensymble.__email__ long_description = ensymble.__doc__ url = "http://code.google.com/p/ensymble/" download_url = "%s/detail?name=%s-%s.tar.gz" % (url, name, version) setup( name = name, version = version, description = "Tools to make PyS60 applications for Symbian S60 phones", author = author, maintainer = maintainer, maintainer_email = maintainer_email, url = url, long_description = long_description, download_url = download_url, packages = ['ensymble', 'ensymble.actions', 'ensymble.utils'], scripts = ['ensymble.py'] ) ensymble-0.29/COPYING0000664000076400007640000004313311373504343013374 0ustar stevesteve GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc. 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Library General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively convey the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. Copyright (C) This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA Also add information on how to contact you by electronic and paper mail. If the program is interactive, make it output a short notice like this when it starts in an interactive mode: Gnomovision version 69, Copyright (C) year name of author Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, the commands you use may be called something other than `show w' and `show c'; they could even be mouse-clicks or menu items--whatever suits your program. You should also get your employer (if you work as a programmer) or your school, if any, to sign a "copyright disclaimer" for the program, if necessary. Here is a sample; alter the names: Yoyodyne, Inc., hereby disclaims all copyright interest in the program `Gnomovision' (which makes passes at compilers) written by James Hacker. , 1 April 1989 Ty Coon, President of Vice This General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Library General Public License instead of this License. ensymble-0.29/PKG-INFO0000664000076400007640000000172711374344735013451 0ustar stevesteveMetadata-Version: 1.0 Name: ensymble Version: 0.29 Summary: Tools to make PyS60 applications for Symbian S60 phones Home-page: http://code.google.com/p/ensymble/ Author: Steven Fernandez Author-email: steve@lonetwin.net License: UNKNOWN Download-URL: http://code.google.com/p/ensymble//detail?name=ensymble-0.29.tar.gz Description: The Ensymble developer utilities for Symbian OS™ is a collection of Python® modules and command line programs for Symbian OS software development. Current focus of Ensymble development is to provide useful tools for making Python for S60 (PyS60) programs. A long term goal of Ensymble is to provide a cross-platform, open-source way to do Symbian OS software development, supporting Symbian OS versions 9.1 and later. SIS files made with Ensymble work from S60 3rd Edition phones onwards. For 1st and 2nd Edition phones there's py2sisng. Platform: UNKNOWN ensymble-0.29/TODO0000664000076400007640000000050211373504343013022 0ustar stevesteveany2sis: new command to make a SIS out of any file type (needs new C++ stub) Try not to copy/paste so much. Dev-cert-request command. Needs IMEI and capabilities as extra data. Move imports inside run() of each command to speed up program startup. sisfield.py: Better memory efficiency sisfield.py: Cached tostring() ensymble-0.29/README0000664000076400007640000012712411373504343013224 0ustar stevesteve The Ensymble developer utilities for Symbian OS Copyright 2006, 2007, 2008, 2009 Jussi Ylänen Last updated 2009-01-30 ABOUT ===== This is the Ensymble developer utilities for Symbian OS(TM), a collection of Python® modules and command line programs for Symbian OS [1] software development. Current focus of Ensymble development is to provide useful tools for making "Python for S60" [2] (also called PyS60) programs. Supported functions include generation of SIS (installation) packages, merging several SIS packages into one, (re-)signing existing SIS packages and modifying extension DLL headers. Support for other Symbian OS software development tasks will follow. A long term goal of Ensymble is to provide a cross-platform, open-source way to do Symbian OS software development, supporting Symbian OS versions 9.1 and later. The original tools by Symbian are closed source and only available for the Windows® operating system. Symbian OS is the operating system used by Nokia [3] in some of its mobile phones [4]. Other manufacturers have also licensed Symbian OS. Python for S60 is a port of the popular Python programming language [5] to a Nokia phone platform called S60® [6]. Before November 2005, S60 was called Series 60. VERSION COMPATIBILITY ===================== Ensymble targets Symbian OS v9.1 and later. For Nokia phones, this means S60 3rd Edition. 1st and 2nd Edition phones are not directly supported by Ensymble. A program called py2sisng [7] supports 1st and 2nd Edition phones and can be used for a subset of tasks that Ensymble performs. The py2sis command of the Ensymble command line tool produces installation packages (SIS) for Python for S60 version 1.4.0 and later. Version 1.4.0 of Python for S60 is the first officially signed release from Nokia. Although packages generated with Ensymble also work with PyS60 from v1.3.8 upto v1.3.23, a harmless warning is generated during installation for a missing Python for S60 component. Ensymble is written in Python. Python v2.2 or newer is required, as older versions lack necessary language features. Ensymble has been tested on the following systems: * Debian GNU/Linux Sid (i386) with Python v2.3.5, v2.4.5 and v2.5.2 * Red Hat Linux release 9 (i386) with Python v2.2.2 * Red Hat Enterprise Linux AS release 3 (i386) with Python v2.2.3 * Red Hat Enterprise Linux AS release 4 (i386) with Python v2.3.4 * Apple OS X Tiger (G4) with Python v2.3.5 * Apple OS X Leopard (i386) with Python v2.5 * Microsoft Windows XP, SP2 with Python v2.5 Note: Python for S60 versions 1.3.20 and older require that SIS files generated by Ensymble are installed into main phone memory instead of memory card. This bug has been corrected in version 1.3.21 of PyS60. INSTALLATION ============ Care has been taken to ensure that minimum amount of extra software is needed to run Ensymble. A working installation of Python is required (see VERSION COMPATIBILITY above). In addition, the OpenSSL command line tool [8] is required for installation package (SIS) generation, merging and (re-)signing. Any recent version will do, and can usually be found pre-installed. For Windows, the Stunnel OpenSSL binaries [9] are recommended, but any other binaries will do as well. The Ensymble command line tool is normally installed as a single file "ensymble.py". This file contains everything except the OpenSSL command line tool and is created using Fredrik Lundh's nifty squeeze utility [10]. Pre-squeezed files for various Python versions may be downloaded from the Ensymble home page. To find out which version to download, type "python -V" on the command line: $ python -V Python 2.4.4 or C:\> python -V Python 2.4.4 A pre-squeezed version for Python 2.4 would be the correct one in this case. Users on Unix-like systems (Linux, *BSD, Apple OS X) can download the correct pre-squeezed version and put in in the "bin" directory under the user's home directory (if such a thing exists), and possibly rename the file to "ensymble.py" as well. Windows users need to download "openssl.zip" (NOT "stunnel-n.nn.exe" or "stunnel-n.nn-installer.exe") [9] and unpack it somewhere, for example "C:\Ensymble", and then put the correct pre-squeezed version of Ensymble there as well, renamed to "ensymble.py": C:\Ensymble\ [directory] ensymble.py [76046 bytes] openssl.exe [1153024 bytes] libssl32.dll [632226 bytes] libeay32.dll [1578787 bytes] To allow using the Ensymble command line tool without explicitly giving a full command path each time, it is advisable to add the installation directory ("C:\Ensymble" in the example above) in the Path environment variable in Windows' environment variables dialog (My Computer -> Properties -> Advanced -> Environment Variables -> System Variables -> Path). The Command Prompt window must be restarted after the change. Installation is also possible from the original source package. For Unix-like systems, there is a simple installation script which squeezes all the required files together and copies the resulting package to a given directory (which must exist already): $ ./install.sh ~/bin USAGE ===== DESCRIPTION The Ensymble command line tool provides access to most Ensymble functionality: $ ensymble.py command options... , where "command" is the command to be executed and "options" are the command specific options. Running ensymble.py without arguments will list all the available commands. The following commands are currently supported by Ensymble: altere32 Alter the IDs and capabilities of e32image files (EXEs, DLLs) genuid Generate a new test-range UID from a name infoe32 Show the IDs and capabilities of e32image files (EXEs, DLLs) mergesis Merge several SIS packages into one py2sis Create a SIS package for a "Python for S60" application signsis Sign a SIS package simplesis Create a SIS package from a directory structure version Print Ensymble version Each command is documented in detail below. Note about security: When using SIS certificates, the private key of the certificate is saved unencrypted to a temporary file and could be recovered by others. This is due to compatibility with old versions of OpenSSL. In the future, Ensymble may require a more recent version of OpenSSL. COMMON OPTIONS --encoding=terminal,filesystem -e terminal,filesystem Local character encodings for terminal and filesystem. For example "--encoding=utf8,latin1", if the terminal is using a UTF-8 character set and the filesystem is still using the older latin1 (ISO-8859-1) character set. See [11] for a list of Python standard encodings. The encodings will be autodetected most of the time, but on some special environments this may not be possible, hence this option. --verbose -v Print extra statistics, such as file names, option summary. --help -h On-line help for a command can be found using this option. EXAMPLES $ ensymble.py Ensymble developer utilities for Symbian OS usage: ensymble.py command [command options]... Commands: altere32 Alter the IDs and capabilities of e32image files (EXEs, DLLs) genuid Generate a new test-range UID from a name infoe32 Show the IDs and capabilities of e32image files (EXEs, DLLs) mergesis Merge several SIS packages into one py2sis Create a SIS package for a "Python for S60" application signsis Sign a SIS package simplesis Create a SIS package from a directory structure version Print Ensymble version Use 'ensymble.py command --help' to get command specific help. When no commands and options are given, Ensymble prints a short help. $ ensymble.py version -h Ensymble developer utilities for Symbian OS usage: ensymble.py version Print Ensymble version. Each command has an on-line help. A short description of what the command does and a list of options are printed. The "altere32" command ---------------------- SYNOPSIS $ ensymble.py altere32 [--uid=0x01234567] [--secureid=0x01234567] [--vendorid=0x01234567] [--caps=Cap1+Cap2+...] [--encoding=terminal,filesystem] [--verbose] or $ ensymble.py altere32 [--uid=0x01234567] [--secureid=0x01234567] [--vendorid=0x01234567] [--caps=Cap1+Cap2+...] [--encoding=terminal,filesystem] [--verbose] --inplace ... DESCRIPTION The "altere32" command alters the IDs and capabilities of e32image files (Symbian EXEs and DLLs). Extension module authors can use this command to quickly generate variations of extension modules without recompiling. PARAMETERS infile Path of the original e32image file. If option "--inplace" (see below) is set, there may be more than one file name present. outfile Path of the modified e32image file. Only used when not using option "--inplace", see below. If a directory name is given, input file name is used as the output file name. --inplace -i Allow more than one input file name, modify input files in-place. Note: Use this option with caution, as no backups of the original files will be made! --uid=0x01234567 -u 0x01234567 Symbian UID for the e32image. This is normally same as the secure ID, see below. If this option is not given, the UID is not changed. --secureid=0x01234567 -s 0x01234567 Secure ID for the e32image. This is normally same as the UID, see above. If this option is not given, the secure ID is not changed. --vendorid=0x01234567 -r 0x01234567 Vendor ID for the e32image. In most cases the vendor ID is 0. If this option is not given, the vendor ID is not changed. --caps=Cap1+Cap2+... -b Cap1+Cap2+... or --caps=0x12345 -b 0x12345 Capability names separated by "+" or a numeric capability bitmask. If no capability option is given, capabilities are not changed. --heapsize=min,max -H min,max Heap size limits for the e32image, only valid for EXEs. If no heap size option is given, the heap size limits are not changed. If only one value is given, it is used as both the heap minimum value and heap maximum value. Values may have suffix "k" or "M" (case insensitive) to denote kilobytes (1024 bytes) and megabytes (1048576 bytes), respectively. The heap minimum value determines if a program is allowed to start. If less than the set amount of memory is available, program start-up fails. The heap maximum value limits the memory a program can allocate. Memory allocations beyond the set limit will fail. EXAMPLES Note: The command lines below may be wrapped over multiple lines due to layout constraints. In reality, each of them should be contained in one physical command line, with no spaces around the "+" characters. $ ensymble.py altere32 --caps=LocalServices+Location+NetworkServices+PowerMgmt+ProtServ+ ReadUserData+SurroundingsDD+SWEvent+UserEnvironment+WriteUserData+ ReadDeviceData+TrustedUI+WriteDeviceData myextension_orig.pyd myextension.pyd This will modify "myextension_orig.pyd" (a "Python for S60" extension DLL) with the capabilities listed, and generate file "myextension.pyd" with the new capabilities. $ ensymble.py altere32 --caps=0xff1b4 --inplace *.dll This modifies every DLL file in the current directory using the same capabilities as above but in a numeric form. Original files will be modified, so use the "--inplace" option with caution! $ ensymble.py altere32 --heapsize=4k,4M myapp_orig.exe myapp.exe Program "myapp.exe" is modified so that it can allocate upto four megabytes of memory. Heap minimum value is usually kept at the default four kilobytes. Note: When modifying the UID, the secure ID should be modified accordingly. Modifying UIDs of application EXEs is generally not possible, because applications usually include the UID in program code as well. The "genuid" command -------------------- SYNOPSIS $ ensymble.py genuid [--encoding=terminal,filesystem] [--verbose] ... DESCRIPTION The "genuid" command generates a test-range UID from an application or extension module name. It is suggested that Ensymble-generated UIDs are used instead of choosing an arbitrary test-range UID, to prevent clashes with software from other authors. PARAMETERS name One or more names for which UIDs are generated. Names are case-insensitive and fully support the international Unicode characters. Generated UIDs are compatible with the automatic UID generation of py2sis and simplesis commands. The name must not contain version information or any file prefixes, just the name itself, e.g. "myextension" instead of "myextension_v1.2.3.sis". EXAMPLES $ ensymble.py genuid myapplication myextension myapplication: 0xe0942bea myextension: 0xe325ed58 This will generate two UIDs: one for "myapplication" and another for "myextension". The "infoe32" command --------------------- SYNOPSIS $ ensymble.py infoe32 [--encoding=terminal,filesystem] [--verbose] ... DESCRIPTION The "infoe32" command displays information about Symbian e32image files (Symbian EXEs and DLLs). All three UIDs as well as the vendor ID and secure ID are printed. Capabilities are displayed textually and as a hexadecimal number. PARAMETERS infile One or more e32image files to inspect. EXAMPLES $ ensymble.py infoe32 myprogram.exe somelibrary.dll myprogram.exe: UID1 0x1000007a UID2 0x100039ce UID3 0x12345678 Secure ID 0x12345678 Vendor ID 0x00000000 Capabilities 0x0 (NONE) somelibrary.dll: UID1 0x10000079 UID2 0x00000000 UID3 0x00000000 Secure ID 0x00000000 Vendor ID 0x00000000 Capabilities 0xff7be (ALL-TCB-DRM-AllFiles) This will display information about "myprogram.exe" and "somelibrary.dll". The "mergesis" command ---------------------- SYNOPSIS $ ensymble.py mergesis [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] [--encoding=terminal,filesystem] [--verbose] [mergefile]... DESCRIPTION The "mergesis" command takes a set of SIS files and inserts them as unconditional embedded SIS files in the first one. The resulting SIS package is then signed with the certificate provided. None of the certificates of the first SIS file are preserved. Note: The "mergesis" command will only work with SIS files that do not already contain other embedded SIS files. PARAMETERS infile Path of the base SIS file. mergefile Zero or more SIS files to embed in the base sis file. outfile Path of the resulting SIS file. If a directory name is given, base SIS file name is used as the output file name. --cert=mycert.cer -a mycert.cer Certificate to use for signing in PEM (text) format. If no certificate and its private key are given, Ensymble uses a default self-signed certificate (see option "--cert" for command "py2sis" above). --privkey=mykey.key -k mykey.key Private key of the certificate in PEM (text) format. If option "--cert" (above) is given, this option is required as well. --passphrase=12345 -p 12345 Pass phrase of the private key. Note: Using command line options to give the pass phrase is insecure. Any user of the computer will be able to see command lines of started programs and thus will see the pass phrase in plain view. Instead, standard input should be used (see examples below). If no pass phrase is given on the command line or standard input, it will be asked interactively. EXAMPLES Note: The command lines below may be wrapped over multiple lines due to layout constraints. In reality, each of them should be contained in one physical command line. $ ensymble.py mergesis --cert=mycert.cer --key=mykey.key --passphrase=12345 myapp_v1_0_0.sis PythonForS60_1_3_17_3rdEd_selfsigned.SIS myapp_standalone_v1_0_0.sis A Python for S60 script "myapp_v1_0_0.sis" will be merged with Python runtime SIS "PythonForS60_1_3_17_3rdEd_selfsigned.SIS". A new SIS file "myscript_standalone_v1_0_0.sis" will be created and signed with "mycert.cer" using private key "mykey.key". $ echo "12345" | ensymble.py mergesis --cert=mycert.cer --key=mykey.key basefile.sis addon1.sis addon2.sis Pass phrase can be given in Ensymble standard input, so that it will not be visible to all users of the computer (see option "--passphrase" above). The "py2sis" command -------------------- SYNOPSIS $ ensymble.py py2sis [--uid=0x01234567] [--appname=AppName] [--version=1.0.0] [--lang=EN,...] [--icon=icon.svg] [--shortcaption="App. Name",...] [--caption="Application Name",...] [--drive=C] [--textfile=mytext_%C.txt] [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] [--caps=Cap1+Cap2+...] [--vendor="Vendor Name",...] [--autostart] [--encoding=terminal,filesystem] [--verbose] [sisfile] DESCRIPTION The "py2sis" command is used to pack a Python for S60 application script and its auxiliary files (if any) into a Symbian installation package (SIS). PARAMETERS src The source script or directory name. When a directory name is given, the directory structure is preserved under an application specific private directory ("\private\\") on the phone. A file called "default.py" is required to exist on the root of the directory given. This will be the main file that starts the application. (See option "--extrasdir" below for more options for file placement.) When a regular file name is given, it will be located under the application specific private directory, with the name "default.py". sisfile Path of the SIS file to create. If a directory name is given, output file name is derived from input file name and application version (see option "--version" below). This is also the case when no SIS file name is given. --appname=AppName -n AppName Name of the application. If no application name is given, it will be derived from the input file name. This name is used as the base for all generated file names (EXE, icon, resources) on the phone. It will also be used as the default caption if none are given, and a temporary UID (see option "--uid" below) will be generated from application name if not given explicitly. --uid=0x01234567 -u 0x01234567 Symbian UID for the application. If the UID option is not given, the main Python file ("default.py", see parameter "src" above) will be scanned for a special string. If no UID can be found, a temporary UID is generated from the application name (see option "--appname" and command "genuid" above). The special UID string can appear anywhere in the main Python file, including comments and as part of string literals. It is of the form SYMBIAN_UID = UID , where whitespace around the equals sign is optional. UID is in the same format as with the command line parameter (see option "--uid" above). Note: Use a real UID for all applications to be distributed. The auto-generated test-range UID is only meant for development and testing, not for real applications. UIDs can be ordered from Symbian Signed [12]. If you have already ordered UIDs for 1st or 2nd Edition phones, to use these UIDs in 3rd Edition phones the first hex digit (a "1") needs to be changed to an "f". Also note: When changing the application name (or source file / directory name in case no application name is explicitly given), the temporary UID changes and the SIS package will be considered a new application on the phone, unless an UID is explicitly specified. --version=1.0.0 -r 1.0.0 Application version: X.Y.Z or X,Y,Z (major, minor, build). There may be a "v" or "V" in front of the major number. Minor and build numbers are optional. If the version option is not given, the main Python file ("default.py", see parameter "src" above) will be scanned for a special string. If no version can be found, it defaults to 1.0.0. The special version string can appear anywhere in the main Python file, including comments and as part of string literals. It is of the form SIS_VERSION = "1.0.0" , where whitespace around the equals sign is optional. Version is in the same format as with the command line parameter (see option "--version" above), except that it may optionally be enclosed in single or double quotes. --lang=EN,... -l EN,... Comma separated list of two-character language codes. These are the languages that the SIS file claims to support, English by default. Application must then somehow determine which language was selected during install. Symbian installation tools reference [13] lists the available language codes. --icon=icon.svg -i icon.svg Icon file in SVG-Tiny format. If no icon is given, the Python logo is used as the icon. The Python logo is a trademark of the Python Software Foundation. Ensymble does not support the old style MBM bitmap icons. --shortcaption="App. Name",... -s "App. Name",... --caption="Application Name",... -c "Application Name",... Comma separated list of short and long captions in all selected languages, i.e. there must be as many comma separated captions as there are languages listed with the "--lang" option. If no captions are given, application name is used (see option "--appname" above). If only the short captions are given, long captions will use those instead. Captions are visible names used in various places on the phone display. Short caption is displayed under the application icon when using the grid layout. Long caption is used on top of the screen when the application is launched and beside the icon in list layout. Long caption is also used as the package name, which is displayed during application installation. --drive=C -f C Installation drive "C" or "E" or "any", to select where the SIS is to be installed. Default is "any", which causes the phone to ask the user where to install the package. --extrasdir=root -x root Name of "extras" directory under the application source directory. The extras directory contains a directory tree which is placed under the root of the installation drive, instead of in the application private directory. If no extras directory name is given, this feature is disabled. Option "--extrasdir" is similar to the "simplesis" command (see below). It allows placing files under "\sys\bin" and "\resource", for example. --textfile=mytext_%C.txt -t mytext_%C.txt Text file (or pattern, see below) to display during install. If none is given, no extra text will be displayed during install. Files to display are in UTF-8 encoding, without Byte-Order Marker (BOM). The file name may contain formatting characters that are substituted for each selected language (see option "--lang" above). If no formatting characters are present, the same text will be used for all languages. %% - literal % %n - language number (01 - 99) %c - two-character language code in lowercase letters %C - two-character language code in capital letters %l - language name in English, using only lowercase letters %l - language name in English, using mixed case letters For example, if there are files named "mytext_EN.txt", "mytext_GE.txt" and "mytext_SP.txt", a pattern of "mytext_%C.txt" will be able to use them. --cert=mycert.cer -a mycert.cer Certificate to use for signing in PEM (text) format. SIS files for Symbian OS v9 and later are required to be digitally signed. Unsigned packages will not install on the phone. A self-generated ("self-signed" in crypto parlance) certificate will do, but only a restricted subset of features ("capabilities", see option "--caps" below) are available for self-signed applications. If no certificate and its private key are given, Ensymble uses a default self-signed certificate. Software authors are encouraged to create their own unique certificates for SIS packages that are to be distributed. --privkey=mykey.key -k mykey.key Private key of the certificate in PEM (text) format. If option "--cert" (above) is given, this option is required as well. --passphrase=12345 -p 12345 Pass phrase of the private key. Note: Using command line options to give the pass phrase is insecure. Any user of the computer will be able to see command lines of started programs and thus will see the pass phrase in plain view. Instead, standard input should be used (see examples below). If no pass phrase is given on the command line or standard input, it will be asked interactively. --caps=Cap1+Cap2+... -b Cap1+Cap2+... or --caps=0x12345 -b 0x12345 Capability names separated by "+" or a numeric capability bitmask. If no capability option is given, the application will not have any special capabilities. Symbian Signed [12] has an FAQ which explains all the available capabilities. --vendor=Name,... -d Name,... Comma separated list of vendor names in all selected languages, i.e. there must be as many comma separated vendor names as there are languages listed with the "--lang" option. Alternatively, if only one vendor name is given, it will be used for all languages. If no vendor names are given then "Ensymble" will be used. Vendor name is visible during installation and on the Application manager, except when using self-signed certificates (see option "--cert" above). --autostart -g Flag to control automatic startup of the application. On S60 3rd Edition phones, an application can register itself to be automatically started when the phone is turned on. Note: Self-signed applications and applications with UID in the unprotected range cannot register to be automatically started (see options "--uid" and "--cert" above). --runinstall -R Run the application after installation. After copying all files to the phone, the application is automatically started. Note: Phones will ignore this flag in SIS files using self-signed certificates (see option "--cert" above). --heapsize=min,max -H min,max Heap size limits for the application. Defaults of 4 kilobytes and one megabyte are used if no heap size option is given. If only one value is given, it is used as both the heap minimum and heap maximum value. Values may have suffix "k" or "M" (case insensitive) to denote kilobytes (1024 bytes) and megabytes (1048576 bytes), respectively. The heap minimum value determines if a program is allowed to start. If less than the set amount of memory is available, program start-up fails. The heap maximum value limits the memory a program can allocate. Memory allocations beyond the set limit will fail. The default of one megabyte is usually enough, but processing large datasets such as images from an integrated camera might require setting the heap maximum value higher. EXAMPLES Note: The command lines below may be wrapped over multiple lines due to layout constraints. In reality, each of them should be contained in one physical command line. $ ensymble.py py2sis myprog.py This generates a SIS file called "myprog_v1_0_0.sis" in current working directory. This SIS file is ready to be uploaded to a S60 3rd Edition phone. Package version defaults to 1.0.0, a test-range UID is auto-generated for the package and a default self-signed certificate is used to digitally sign the SIS file. A default icon (the Python logo) is used for application icon. $ echo "12345" | ensymble.py py2sis --cert mycert.cer --privkey mykey.key myprog.py Pass phrase can be given in Ensymble standard input, so that it will not be visible to all users of the computer (see option "--passphrase" above). The "signsis" command --------------------- SYNOPSIS $ ensymble.py signsis [--unsign] [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] [--execaps=Cap1+Cap2+...] [--dllcaps=Cap1+Cap2+...] [--encoding=terminal,filesystem] [--verbose] [outfile] DESCRIPTION The "signsis" command (re-)signs a SIS package with the certificate provided, stripping out any existing certificates, if any. Capabilities of all EXE and DLL files contained in the SIS package can optionally be modified. Extension module authors may want to distribute a version of their SIS packages with only limited capabilities. Users can then adjust the capabilities according to their own needs and sign the SIS package with their own certificates. Note: SIS packages may embed other SIS packages. The "signsis" command will only alter the main SIS, leaving all embedded SIS packages intact. PARAMETERS infile Path of the original SIS file. outfile Path of the modified SIS file. If a directory name is given, input file name is used as the output file name. Note: If no output file name is given, original SIS file is overwritten! --unsign -u Instead of signing a SIS file, remove all signatures from it. This is useful when submitting the SIS file to an external signing authority, otherwise the Ensymble default certificate may remain in the resulting signed SIS file. --cert=mycert.cer -a mycert.cer Certificate to use for signing in PEM (text) format. If no certificate and its private key are given, Ensymble uses a default self-signed certificate (see option "--cert" for command "py2sis" above). --privkey=mykey.key -k mykey.key Private key of the certificate in PEM (text) format. If option "--cert" (above) is given, this option is required as well. --passphrase=12345 -p 12345 Pass phrase of the private key. Note: Using command line options to give the pass phrase is insecure. Any user of the computer will be able to see command lines of started programs and thus will see the pass phrase in plain view. Instead, standard input should be used (see examples below). If no pass phrase is given on the command line or standard input, it will be asked interactively. --execaps=Cap1+Cap2+... -b Cap1+Cap2+... or --execaps=0x12345 -b 0x12345 Capability names separated by "+" or a numeric capability bitmask. EXEs inside the SIS file will be modified according to these capabilities. If no capability option is given, no EXEs will be modified. Symbian Signed [12] has an FAQ which explains all the available capabilities. --dllcaps=Cap1+Cap2+... -d Cap1+Cap2+... or --dllcaps=0x12345 -d 0x12345 Capability names separated by "+" or a numeric capability bitmask. DLLs inside the SIS file will be modified according to these capabilities. If no capability option is given, no DLLs will be modified. EXAMPLES Note: The command lines below may be wrapped over multiple lines due to layout constraints. In reality, each of them should be contained in one physical command line, with no spaces around the "+" characters. $ ensymble.py signsis --dllcaps=LocalServices+Location+NetworkServices+PowerMgmt+ProtServ+ ReadUserData+SurroundingsDD+SWEvent+UserEnvironment+WriteUserData+ ReadDeviceData+TrustedUI+WriteDeviceData --cert=mycert.cer --key=mykey.key --passphrase=12345 coolextension_nocert.sis coolextension_mycert.sis Extension module "coolextension_nocert.sis" will be modified with the capabilities listed. A new SIS file "coolextension_mycert.sis" will be created and signed with "mycert.cer" using private key "mykey.key". $ echo "12345" | ensymble.py signsis --dllcaps=0xff1b4 --cert=mycert.cer --key=mykey.key coolextension_nocert.sis coolextension_mycert.sis Pass phrase can be given in Ensymble standard input, so that it will not be visible to all users of the computer (see option "--passphrase" above). The "simplesis" command ----------------------- SYNOPSIS $ ensymble.py simplesis [--uid=0x01234567] [--version=1.0.0] [--lang=EN,...] [--caption="Package Name",...] [--drive=C] [--textfile=mytext_%C.txt] [--cert=mycert.cer] [--privkey=mykey.key] [--passphrase=12345] [--vendor="Vendor Name",...] [--encoding=terminal,filesystem] [--verbose] [sisfile] DESCRIPTION The "simplesis" command is used to create a Symbian installation package (SIS) from an existing directory structure. Conditional inclusion of files, dependencies or other fancy features of SIS files are not supported. PARAMETERS srcdir The source directory name. The directory structure is preserved under the installation drive, i.e files and directories under "srcdir" will be located on the root of the installation drive. sisfile Path of the SIS file to create. If a directory name is given, output file name is derived from source directory name and package version (see option "--version" below). This is also the case when no SIS file name is given. --uid=0x01234567 -u 0x01234567 Symbian UID for the package. If the UID option is not given, the source directory name will be used to generate a temporary UID (see command "genuid" above). Note: Use a real UID for all packages to be distributed. The auto-generated test-range UID is only meant for development and testing, not for real packages. UIDs can be ordered from Symbian Signed [12]. If you have already ordered UIDs for 1st or 2nd Edition phones, to use these UIDs in 3rd Edition phones the first hex digit (a "1") needs to be changed to an "f". Also note: When changing the source directory name, the temporary UID changes and the SIS package will be considered a new application on the phone. --version=1.0.0 -r 1.0.0 Package version: X.Y.Z or X,Y,Z (major, minor, build). There may be a "v" or "V" in front of the major number. Minor and build numbers are optional. If the version option is not given it defaults to 1.0.0. --lang=EN,... -l EN,... Comma separated list of two-character language codes. These are the languages that the SIS file claims to support, English by default. Symbian installation tools reference [13] lists the available language codes. --caption="Application Name",... -c "Application Name",... Comma separated list of package captions in all selected languages, i.e. there must be as many comma separated captions as there are languages listed with the "--lang" option. If no captions are given, source directory name is used (see parameter "srcdir" above). Package caption is displayed during application installation. --drive=C -f C Installation drive "C" or "E" or "any", to select where the SIS is to be installed. Default is "any", which causes the phone to ask the user where to install the package. --textfile=mytext_%C.txt -t mytext_%C.txt Text file (or pattern, see below) to display during install. If none is given, no extra text will be displayed during install. Files to display are in UTF-8 encoding, without Byte-Order Marker (BOM). The file name may contain formatting characters that are substituted for each selected language (see option "--lang" above). If no formatting characters are present, the same text will be used for all languages. %% - literal % %n - language number (01 - 99) %c - two-character language code in lowercase letters %C - two-character language code in capital letters %l - language name in English, using only lowercase letters %l - language name in English, using mixed case letters For example, if there are files named "mytext_EN.txt", "mytext_GE.txt" and "mytext_SP.txt", a pattern of "mytext_%C.txt" will be able to use them. --cert=mycert.cer -a mycert.cer Certificate to use for signing in PEM (text) format. SIS files for Symbian OS v9 and later are required to be digitally signed. Unsigned packages will not install on the phone. A self-generated ("self-signed" in crypto parlance) certificate will do, but only a restricted subset of features ("capabilities", see option "--caps" below) are available for self-signed applications. If no certificate and its private key are given, Ensymble uses a default self-signed certificate. Software authors are encouraged to create their own unique certificates for SIS packages that are to be distributed. --privkey=mykey.key -k mykey.key Private key of the certificate in PEM (text) format. If option "--cert" (above) is given, this option is required as well. --passphrase=12345 -p 12345 Pass phrase of the private key. Note: Using command line options to give the pass phrase is insecure. Any user of the computer will be able to see command lines of started programs and thus will see the pass phrase in plain view. Instead, standard input should be used (see examples below). If no pass phrase is given on the command line or standard input, it will be asked interactively. EXAMPLES Note: The command lines below may be wrapped over multiple lines due to layout constraints. In reality, each of them should be contained in one physical command line. $ ls -R mymodule mymodule: resource/ sys/ mymodule/resource: apps/ mymodule.py mymodule/resource/apps: mymodule.rsc mymodule/sys: bin/ mymodule/sys/bin: _mymodule.pyd $ ensymble.py simplesis mymodule This generates a SIS file called "mymodule_v1_0_0.sis" in current working directory. Contents of the SIS file are directly taken from directory "mymodule" and the relative path of each file is preserved. The SIS file is ready to be uploaded to a S60 3rd Edition phone. Package version defaults to 1.0.0, a test-range UID is auto-generated for the package and a default self-signed certificate is used to digitally sign the SIS file. $ echo "12345" | ensymble.py py2sis --cert mycert.cer --privkey mykey.key mymodule Pass phrase can be given in Ensymble standard input, so that it will not be visible to all users of the computer (see option "--passphrase" above). The "version" command --------------------- SYNOPSIS $ ensymble.py version DESCRIPTION The "version" command prints Ensymble version. PARAMETERS No parameters EXAMPLES $ ensymble.py version Ensymble v0.28 2009-01-30 The version string is printed. PROJECT HISTORY =============== 2009-01-30 Released Ensymble version v0.28 2009-01-30. Added genuid command for generating test-range UIDs. Added --unsign option to the signsis command. Corrected a bug where DLLs (PYDs) would fail with the py2sis --extrasdir option. Corrected an encoding bug in simplesis automatic UID generation. Corrected a possible Unicode exception in py2sis automatic UID generation. Added a warning about manually specifying a different test-range UID. 2008-10-17 Converted everything from ISO-8859-1 to UTF-8 encoding. 2008-06-30 Released Ensymble version v0.27 2008-06-30. Implemented --heapsize option for the py2sis and altere32 commands. Implemented --extrasdir option for the py2sis command for more flexibility. Added support for PKCS#8 format private keys which Symbian sends to people. 2008-01-27 Released Ensymble version v0.26 2008-01-27. Incorporated a --runinstall option for the py2sis command, by Jari Kirma. Commands mergesis and signsis no longer choke on extra bytes in input SIS files. 2007-12-15 Released Ensymble version v0.25 2007-12-15. Added --drive option to py2sis and simplesis commands, for installation drive. Added --vendor option for the simplesis command, as well. The OpenSSL command line tool can reside in the same directory as Ensymble now. Prevent leaving zero-byte output files behind when no OpenSSL tool is found. Made it possible to use numeric capability bitmasks. Rewrote installation instructions to better take Windows users into account. 2007-10-18 Released Ensymble version v0.24 2007-10-18. Added --autostart option to the py2sis command, like in the official py2sis. Added --vendor option to the py2sis command, useful when signing packages. Added infoe32 command contributed by Martin Storsjö. 2007-07-16 Released Ensymble version v0.23 2007-07-16. Python for S60 changed its UID due to Nokia finally signing it, adapted. Clarified README in using embedded version and UID strings with py2sis command. 2007-02-08 Released Ensymble version 2007-02-08 v0.22. Added simplesis command for creating a SIS package out of a directory structure. Added maximum file size sanity check for py2sis command. 2007-02-01 Released Ensymble version 2007-02-01 v0.21. Added mergesis command for combining several SIS files into one. 2007-01-01 Released Ensymble version 2007-01-01 v0.20. Revamped documentation. Now every command and option is explained. Added signsis command for (re-)signing SIS files. Added altere32 command for altering pre-compiled Symbian EXEs and DLLs. Implemented text file option for py2sis command ("Freeware Route to Market"). Made py2sis language option more robust against mistyped language codes. Made it possible modify signatures of an existing SISController object. Generated capability fields are smaller now (4 bytes instead of 8 bytes). Moved default certificate to its own module: defaultcert.py. Made exception dumps more verbose when the --debug option is present. Miscellaneous clean-ups. 2006-11-18 Released Ensymble version 2006-11-18 v0.15. Rewrote certificate to binary conversion. Fixes problems on Windows. Removed line feed after pass phrase for OpenSSL. It will not work on Windows. 2006-11-12 Released Ensymble version 2006-11-12 v0.14. Modified the generated SIS a bit, to be compatible with native signsis.exe. Test UIDs use lower case appname now, due to Symbian OS being case insensitive. Added support for signature chains. Symbian Signed uses those. 2006-10-06 Fourth public preview release: 2006-10-06 v0.13 OpenSSL invocation uses absolute paths now. Windows XP Pro should work now. 2006-10-05 Third public preview release: 2006-10-05 v0.12 Implemented automatic test UID generation for the py2sis command. Added warning message for UIDs in the protected range. Changed OpenSSL path detection to be carried out only on demand. Added debug messages for troubleshooting OpenSSL-related problems. Miscellaneous clean-ups 2006-09-26 Second public preview release: 2006-09-26 v0.11 Made the default certificate a bit more anonymous. Added Windows (NT/2000/XP) support. 2006-09-25 Tested Ensymble on Python v2.2. Added COPYING file to the source package. Minor edits to README and ensymble.html 2006-09-24 First public preview release: 2006-09-24 v0.10 LICENSE ======= Ensymble developer utilities for Symbian OS(TM) Copyright 2006, 2007 Jussi Ylänen Released under the GNU General Public Licence (see file COPYING) CONTACT ======= Official web page for Ensymble can be found at http://www.nbl.fi/jussi.ylanen/ensymble.html Please send comments, suggestions, corrections and enhancements to: jussi.ylanen@iki.fi ACKNOWLEDGEMENTS ================ Symbian and all Symbian-based marks and logos are trade marks of Symbian Limited. "Python" and the Python logo are trademarks or registered trademarks of the Python Software Foundation. Nokia, S60 and logo, Series 60 are trademarks or registered trademarks of Nokia Corporation. Windows is a registered trademark of Microsoft Corporation in the United States and other countries. REFERENCES ========== [1] Symbian OS http://www.symbian.com [2] Python for S60 http://www.forum.nokia.com/python [3] Nokia http://www.nokia.com [4] A list of Nokia S60 phone editions: http://www.forum.nokia.com/devices/matrix_s60_1.html [5] The Python programming language http://www.python.org [6] S60 Platform http://www.s60.com [7] Python-to-SIS, the next generation by Jussi Ylänen http://www.nbl.fi/jussi.ylanen/py2sisng.html [8] OpenSSL http://www.openssl.org [9] Stunnel OpenSSL binaries http://www.stunnel.org/download/binaries.html [10] squeeze utility by Fredrik Lundh http://effbot.org/zone/squeeze.htm [11] Python standard encodings http://docs.python.org/lib/standard-encodings.html [12] Symbian Signed http://www.symbiansigned.com [13] Symbian installation tools reference http://www.symbian.com/developer/techlib/v70sdocs/doc_source/ToolsAndUtilities/Installing-ref/PackageFileFormatReference.guide.html