shithub: choc

Download patch

ref: d131bab501c6d705b6449852d8271dde20245ddd
parent: 9d9d55a9d8d56c1044e89d93e85f21ff2348237a
author: Simon Howard <[email protected]>
date: Sat Jun 20 18:13:44 EDT 2009

Add script to generate Windows CE install package.

Subversion-branch: /trunk/chocolate-doom
Subversion-revision: 1604

--- /dev/null
+++ b/pkg/wince/wince-cab.cfg
@@ -1,0 +1,25 @@
+
+app_name = "Chocolate Doom"
+provider = "Simon Howard"
+arch = "strongarm"
+
+# Install files:
+
+d = "$(PROGRAMS_GAMES)/Chocolate Doom/"
+s = "$(START_GAMES)/"
+
+files = {
+    d+"chocolate-doom.exe":  "../../src/chocolate-doom.exe",
+    d+"chocolate-setup.exe": "../../setup/chocolate-setup.exe",
+    d+"SDL.dll":             "SDL.dll",
+    d+"SDL_mixer.dll":       "SDL_mixer.dll",
+    d+"SDL_net.dll":         "SDL_net.dll",
+}
+
+# Start menu links:
+
+links = {
+    s+"Chocolate Doom.lnk":  d+"chocolate-doom.exe",
+    s+"Chocolate Doom Setup.lnk": d+"chocolate-setup.exe"
+}
+
--- /dev/null
+++ b/pkg/wince/wince-cabgen
@@ -1,0 +1,653 @@
+#!/usr/bin/env python
+
+import os
+import re
+import shutil
+import sys
+import tempfile
+
+CAB_HEADER = "MSCE"
+
+ARCHITECTURES = {
+    "shx-sh3":        103,
+    "shx-sh4":        104,
+    "i386":           386,
+    "i486":           486,
+    "i586":           586,
+    "powerpc-601":    601,
+    "powerpc-603":    603,
+    "powerpc-604":    604,
+    "powerpc-620":    620,
+    "powerpc-mpc821": 821,
+    "arm720":         1824,
+    "arm820":         2080,
+    "arm920":         2336,
+    "strongarm":      2577,
+    "mips-r4000":     4000,
+    "sh3":            10003,
+    "sh3e":           10004,
+    "sh4":            10005,
+    "alpha-21064":    21064,
+    "arm7tdmi":       70001,
+}
+
+DIR_VARIABLES = {
+    "PROGRAMS": "%CE1%",                # \Program Files
+    "WINDOWS": "%CE2%",                 # \Windows
+    "DESKTOP": "%CE3%",                 # \Windows\Desktop
+    "STARTUP": "%CE4%",                 # \Windows\StartUp
+    "DOCUMENTS": "%CE5%",               # \My Documents
+    "PROGRAMS_ACCESSORIES": "%CE6%",    # \Program Files\Accessories
+    "PROGRAMS_COMMUNICATIONS": "%CE7%", # \Program Files\Communications
+    "PROGRAMS_GAMES": "%CE8%",          # \Program Files\Games
+    "PROGRAMS_OUTLOOK": "%CE9%",        # \Program Files\Pocket Outlook
+    "PROGRAMS_OFFICE": "%CE10%",        # \Program Files\Office
+    "WINDOWS_PROGRAMS": "%CE11%",       # \Windows\Programs
+    "WINDOWS_ACCESSORIES": "%CE12%",    # \Windows\Programs\Accessories
+    "WINDOWS_COMMUNICATIONS": "%CE13%", # \Windows\Programs\Communications
+    "WINDOWS_GAMES": "%CE14%",          # \Windows\Programs\Games
+    "FONTS": "%CE15%",                  # \Windows\Fonts
+    "RECENT": "%CE16%",                 # \Windows\Recent
+    "FAVORITES": "%CE17%",              # \Windows\Favorites
+
+    "START_PROGRAMS": "%CE11%",         # \Windows\Start Menu\Programs
+    "START_ACCESSORIES": "%CE12%",      # \Windows\Start Menu\Accessories
+    "START_COMMUNICATIONS": "%CE13%",   # \Windows\Start Menu\Communications
+    "START_GAMES": "%CE14%",            # \Windows\Start Menu\Games
+    "START": "%CE17%",                  # \Windows\Start Menu
+}
+
+def write_int16(f, value):
+    b1 = value & 0xff
+    b2 = (value >> 8) & 0xff
+    f.write("%c%c" % (b1, b2))
+
+def write_int32(f, value):
+    b1 = value & 0xff
+    b2 = (value >> 8) & 0xff
+    b3 = (value >> 16) & 0xff
+    b4 = (value >> 24) & 0xff
+    f.write("%c%c%c%c" % (b1, b2, b3, b4))
+
+# Pad a string with NUL characters so that it has a length that is 
+# a multiple of 4.  At least one NUL is always added.
+
+def pad_string(s):
+    pad_len = 4 - (len(s) % 4)
+    return s + (pad_len * "\x00")
+
+class HeaderSection:
+
+    def __init__(self, cab_header):
+        self.cab_header = cab_header
+        self.arch = None
+        self.app_name = None
+        self.provider = None
+        self.unsupported = None
+
+    def __len__(self):
+        return 100       # header has fixed size
+
+    def set_meta(self, arch, app_name, provider, unsupported):
+
+        if arch not in ARCHITECTURES:
+            raise Exception("Unknown architecture '%s'" % arch)
+
+        self.arch = ARCHITECTURES[arch]
+
+        dictionary = self.cab_header.dictionary
+
+        self.app_name = app_name
+        dictionary.get(self.app_name)
+
+        self.provider = provider
+        dictionary.get(self.provider)
+
+        self.unsupported = unsupported
+        dictionary.get(self.unsupported)
+
+    def write(self, stream):
+
+        # Basic header
+
+        stream.write(CAB_HEADER)
+        write_int32(stream, 0)
+        write_int32(stream, len(self.cab_header))
+        write_int32(stream, 0)
+        write_int32(stream, 1)
+        write_int32(stream, self.arch)
+
+        # minimum Windows CE version:
+        write_int32(stream, 0)
+        write_int32(stream, 0)
+        write_int32(stream, 0)
+        write_int32(stream, 0)
+        write_int32(stream, 0)
+        write_int32(stream, 0)
+
+        dictionary = self.cab_header.dictionary
+
+        # Write number of entries in other sections:
+
+        for section in self.cab_header.sections:
+            if section is not self:
+                write_int16(stream, section.num_entries())
+
+        # Write offsets of other sections:
+
+        for section in self.cab_header.sections:
+            if section is not self:
+                offset = self.cab_header.get_section_offset(section)
+                write_int32(stream, offset)
+
+        # Special strings:
+
+        special_strings = (
+            self.app_name,
+            self.provider,
+            self.unsupported
+        )
+
+        dictionary_offset = self.cab_header.get_section_offset(dictionary)
+
+        for s in special_strings:
+            s_offset = dictionary.get_offset(s)
+            write_int16(stream, dictionary_offset + s_offset)
+            write_int16(stream, len(s) + 1)
+
+        # Two left-over fields of unknown use:
+
+        write_int16(stream, 0)
+        write_int16(stream, 0)
+
+class StringDictionary:
+    def __init__(self, cab_header):
+        self.cab_header = cab_header
+        self.string_list = []
+        self.strings = {}
+        self.length = 0
+        self.index = 1
+
+    # Get the length of the dictionary, in bytes.
+
+    def __len__(self):
+        return self.length
+
+    # Get the number of entries in the dictionary.
+
+    def num_entries(self):
+        return len(self.strings)
+
+    # Get the ID for the given string, adding it if necessary.
+
+    def get(self, s):
+        # Is this a new string?  Add it to the dictionary.
+
+        if s not in self.strings:
+            offset = self.length
+            padded = pad_string(s)
+
+            self.strings[s] = (self.index, offset)
+            self.string_list.append((self.index, padded))
+            self.length += len(padded) + 4
+            self.index += 1
+
+        return self.strings[s][0]
+
+    # Get the offset of a particular string within the dictionary.
+
+    def get_offset(self, s):
+        return self.strings[s][1] + 4
+
+    # Write the dictionary to the output stream.
+
+    def write(self, stream):
+
+        # Write out all strings:
+
+        for i, s in self.string_list:
+            write_int16(stream, i)
+            write_int16(stream, len(s))
+            stream.write(s)
+
+class DirectoryList:
+    def __init__(self, cab_header):
+        self.cab_header = cab_header
+        self.directories_list = []
+        self.directories = {}
+        self.length = 0
+        self.index = 1
+
+    def __len__(self):
+        return self.length
+
+    def num_entries(self):
+        return len(self.directories_list)
+
+    # Find whether the specified directory exists in the list
+
+    def find(self, dir):
+        key = dir.lower()
+
+        if key in self.directories:
+            return self.directories[key]
+        else:
+            return None
+
+    # Get the ID for the given directory, adding it if necessary.
+
+    def get(self, dir):
+
+        key = dir.lower()
+        dictionary = self.cab_header.dictionary
+
+        # Add new directory?
+
+        if key not in self.directories:
+
+            # Separate into individual directories, and map to strings:
+
+            #dir_path = dir.split("\\")
+            #if dir_path[0] == "":
+            #    dir_path = dir_path[1:]
+            dir_path = [ dir ]
+
+            dir_path = map(lambda x: dictionary.get(x), dir_path)
+
+            self.directories[key] = self.index
+            self.directories_list.append((self.index, dir_path))
+            self.length += 6 + 2 * len(dir_path)
+            self.index += 1
+
+        return self.directories[key]
+
+    # Write the directory list to the specified stream.
+
+    def write(self, stream):
+        for i, dir in self.directories_list:
+            write_int16(stream, i)
+            write_int16(stream, 2 * len(dir) + 2)
+
+            for subdir in dir:
+                write_int16(stream, subdir)
+
+            write_int16(stream, 0)
+
+class FileList:
+    def __init__(self, cab_header):
+        self.cab_header = cab_header
+        self.files = []
+        self.length = 0
+        self.index = 1
+
+    # Get the length of this section, in bytes.
+
+    def __len__(self):
+        return self.length
+
+    # Query whether the file list contains a particular file.
+
+    def find(self, filename):
+        dirname, sep, target_basename = filename.rpartition("\\")
+
+        target_basename = pad_string(target_basename)
+
+        target_dir_id = self.cab_header.directory_list.find(dirname)
+
+        if target_dir_id is None:
+            return None
+        else:
+            # Search the list of files:
+
+            for i, dir_id, basename, file_no, flags in self.files:
+                if dir_id == target_dir_id and basename == target_basename:
+                    return file_no
+            else:
+                return None
+
+    # Get the number of entries in the file list
+
+    def num_entries(self):
+        return len(self.files)
+
+    # Add a file to the list.
+
+    def add(self, filename, file_no, flags=0):
+
+        dirname, sep, basename = filename.rpartition("\\")
+
+        dir_id = self.cab_header.directory_list.get(dirname)
+
+        padded = pad_string(basename)
+
+        self.files.append((self.index, dir_id, padded, file_no, flags))
+        self.length += 12 + len(padded)
+        self.index += 1
+
+    # Write this section to the output stream.
+
+    def write(self, stream):
+
+        for i, dir_id, filename, file_no, flags in self.files:
+            write_int16(stream, i)
+            write_int16(stream, dir_id)
+            write_int16(stream, file_no)
+            write_int32(stream, flags)
+            write_int16(stream, len(filename))
+            stream.write(filename)
+
+# TODO?
+
+class RegHiveList:
+    def __len__(self):
+        return 0
+
+    def num_entries(self):
+        return 0
+
+    def write(self, stream):
+        pass
+
+class RegKeyList():
+    def __len__(self):
+        return 0
+
+    def num_entries(self):
+        return 0
+
+    def write(self, stream):
+        pass
+
+class LinkList:
+    def __init__(self, cab_header):
+        self.cab_header = cab_header
+        self.links = []
+        self.length = 0
+        self.index = 1
+
+    def __len__(self):
+        return self.length
+
+    def num_entries(self):
+        return len(self.links)
+    
+    # Determine the target type (dir/file) and ID:
+
+    def __find_target(self, target):
+        file_id = self.cab_header.file_list.find(target)
+
+        if file_id is not None:
+            return 1, file_id
+
+        dir_list = self.cab_header.get_section(DirectoryList)
+        dir_id = dir_list.find(target)
+
+        if dir_id is not None:
+            return 0, dir_id
+
+        raise Exception("Link target '%s' not found" % target)
+
+    def add(self, target, destination):
+
+        target_type, target_id = self.__find_target(target)
+
+        dest_path = destination.split("\\")
+
+        # Leading \:
+
+        if dest_path[0] == "":
+            dest_path = dest_path[1:]
+
+        # %CEn% to specify the install root is handled differently for
+        # links than it is for files/dirs.
+
+        match = re.match(r"\%CE(\d+)\%", dest_path[0])
+
+        if match:
+            base_dir = int(match.group(1))
+            dest_path = dest_path[1:]
+        else:
+            base_dir = 0
+
+        # Map dirs that make up the path to strings.
+
+        dictionary = self.cab_header.dictionary
+        dest_path = map(lambda x: dictionary.get(x), dest_path)
+
+        self.links.append((self.index, target_type, target_id,
+                           base_dir, dest_path))
+        self.index += 1
+        self.length += 14 + 2 * len(dest_path)
+
+    def write(self, stream):
+
+        for i, target_type, target_id, base_dir, dest_path in self.links:
+
+            write_int16(stream, i)
+            write_int16(stream, 0)
+            write_int16(stream, base_dir)
+            write_int16(stream, target_id)
+            write_int16(stream, target_type)
+            write_int16(stream, 2 * len(dest_path) + 2)
+
+            for subdir in dest_path:
+                write_int16(stream, subdir)
+
+            write_int16(stream, 0)
+
+class CabHeaderFile:
+    def __init__(self):
+        self.dictionary = StringDictionary(self)
+        self.directory_list = DirectoryList(self)
+        self.file_list = FileList(self)
+
+        self.sections = [
+            HeaderSection(self),
+            self.dictionary,
+            self.directory_list,
+            self.file_list,
+            RegHiveList(),
+            RegKeyList(),
+            LinkList(self)
+        ]
+
+    def set_meta(self, *args):
+        header_section = self.get_section(HeaderSection)
+        header_section.set_meta(*args)
+
+    def add_file(self, filename, file_no, flags=0):
+        files_section = self.get_section(FileList)
+        files_section.add(filename, file_no, flags)
+
+    def add_link(self, target, destination):
+        links_section = self.get_section(LinkList)
+        links_section.add(target, destination)
+
+    def get_section(self, section_class):
+        for section in self.sections:
+            if isinstance(section, section_class):
+                return section
+        else:
+            raise Exception("Can't find section of class %s" % section_class)
+
+    def get_section_offset(self, section):
+        offset = 0
+
+        for s in self.sections:
+            if section is s:
+                return offset
+            offset += len(s)
+        else:
+            raise Exception("Section %s not found in list")
+
+    def __len__(self):
+        result = 0
+        for s in self.sections:
+            result += len(s)
+        return result
+
+    def write(self, stream):
+        old_pos = 0
+        for section in self.sections:
+            section.write(stream)
+            pos = stream.tell()
+            if pos != old_pos + len(section):
+                raise Exception("Section is %i bytes long, but %i written" % \
+                                (len(section), pos - old_pos))
+            old_pos = pos
+
+class CabFile:
+    def __init__(self, config):
+        self.cab_header = CabHeaderFile()
+
+        self.__process_meta(config)
+        self.__process_files(config["files"])
+
+        if "links" in config:
+            self.__process_links(config["links"])
+
+    # Metadata:
+
+    def __process_meta(self, config):
+        arch = config.get("arch") or "strongarm"
+        app_name = config.get("app_name")
+        provider = config.get("provider")
+        unsupported = config.get("unsupported") or ""
+
+        if app_name is None or provider is None:
+            raise Exception("Application name and provider must be specified")
+
+        self.cab_header.set_meta(arch, app_name, provider, unsupported)
+        self.app_name = app_name
+
+    # Get the shortened 8.3 filename used for the specified file
+    # within the CAB.
+
+    def __shorten_name(self, filename, file_no):
+
+        # Strip down to base filename without extension:
+
+        basename = os.path.basename(filename)
+
+        if "." in basename:
+            basename = basename.rpartition(".")[0]
+
+        # Remove non-alphanumeric characters:
+
+        def only_alnum(x):
+            if x.isalnum():
+                return x
+            else:
+                return ""
+
+        cleaned_name = "".join(map(only_alnum, basename))
+        short_name = cleaned_name[0:8]
+
+        if len(short_name) < 8:
+            short_name = "0" * (8 - len(short_name)) + short_name
+
+        return "%s.%03i" % (short_name, file_no)
+
+    # Process the list of files to install:
+
+    def __process_files(self, files):
+        self.files = [ self.app_name ]
+
+        for filename, source_file in files.items():
+            file_no = len(self.files)
+            filename = expand_path(filename)
+            self.cab_header.add_file(filename, file_no)
+            self.files.append(source_file)
+
+    # Process the list of links:
+
+    def __process_links(self, links):
+        for destination, target in links.items():
+            target = expand_path(target)
+            destination = expand_path(destination)
+            self.cab_header.add_link(target, destination)
+
+    # Write the header file:
+
+    def __write_header(self, dir):
+
+        basename = self.__shorten_name(self.files[0], 0)
+        filename = os.path.join(dir, basename)
+
+        stream = file(filename, "w")
+        self.cab_header.write(stream)
+        stream.close()
+
+        return [ filename ]
+
+    # Write the files:
+
+    def __write_files(self, dir):
+
+        result = []
+
+        for file_no in range(1, len(self.files)):
+            source_file = self.files[file_no]
+            basename = self.__shorten_name(source_file, file_no)
+            filename = os.path.join(dir, basename)
+
+            shutil.copy(source_file, filename)
+            result.append(filename)
+
+        return result
+
+    # Output to a file:
+
+    def write(self, filename):
+
+        temp_dir = tempfile.mkdtemp()
+
+        header = self.__write_header(temp_dir)
+        files = self.__write_files(temp_dir)
+        files.reverse()
+
+        args = [ "lcab", "-n" ] + header + files + [ filename ]
+
+        os.spawnlp(os.P_WAIT, "lcab", *args)
+
+def expand_path(filename):
+
+    # Replace Unix-style / path separators with DOS-style \
+
+    filename = filename.replace("/", "\\")
+
+    # Expand $(xyz) path variables to their Windows equivalents:
+
+    def replace_var(match):
+	var_name = match.group(1)
+
+	if not var_name in DIR_VARIABLES:
+	    raise Exception("Unknown variable '%s'" % var_name)
+	else:
+	    return DIR_VARIABLES[var_name]
+
+    return re.sub(r"\$\((.*?)\)", replace_var, filename)
+
+def read_config_file(filename):
+    f = file(filename)
+
+    data = f.readlines()
+    data = "".join(data)
+
+    f.close()
+
+    prog = compile(data, filename, "exec")
+    result = {}
+    eval(prog, result)
+
+    return result
+
+if len(sys.argv) < 3:
+    print "Usage: %s <config file> <output file>" % sys.argv[0]
+    sys.exit(0)
+
+config = read_config_file(sys.argv[1])
+
+cab_file = CabFile(config)
+cab_file.write(sys.argv[2])
+