示例#1
0
def post_import():
    """
    This is called after import or reload, to do further initialization
    of various modules.
    """

    import renpy  # @UnresolvedImport

    # Create the store.
    renpy.python.create_store("store")

    # Import the contents of renpy.defaultstore into renpy.store, and set
    # up an alias as we do.
    renpy.store = sys.modules['store']
    renpy.exports.store = renpy.store
    sys.modules['renpy.store'] = sys.modules['store']

    import subprocess
    sys.modules['renpy.subprocess'] = subprocess

    for k, v in six.iteritems(renpy.defaultstore.__dict__):
        renpy.store.__dict__.setdefault(k, v)

    renpy.store.eval = renpy.defaultstore.eval

    # Import everything into renpy.exports, provided it isn't
    # already there.
    for k, v in six.iteritems(globals()):
        vars(renpy.exports).setdefault(k, v)
示例#2
0
文件: game.py 项目: susan-shu-c/renpy
def call_replay(label, scope={}):
    """
    :doc: replay

    Calls a label as a memory.

    Keyword arguments are used to set the initial values of variables in the
    memory context.
    """

    renpy.game.log.complete()

    old_log = renpy.game.log
    renpy.game.log = renpy.python.RollbackLog()

    sb = renpy.python.StoreBackup()
    renpy.python.clean_stores()

    context = renpy.execution.Context(True)
    contexts.append(context)

    if renpy.display.interface is not None:
        renpy.display.interface.enter_context()

    # This has to be here, to ensure the scope stuff works.
    renpy.exports.execute_default_statement()

    for k, v in six.iteritems(renpy.config.replay_scope):
        setattr(renpy.store, k, v)

    for k, v in six.iteritems(scope):
        setattr(renpy.store, k, v)

    renpy.store._in_replay = label

    try:

        context.goto_label("_start_replay")
        renpy.execution.run_context(False)

    except EndReplay:
        pass

    finally:

        context.pop_all_dynamic()

        contexts.pop()
        renpy.game.log = old_log
        sb.restore()

        if interface and interface.restart_interaction and contexts:
            contexts[-1].scene_lists.focused = None

        renpy.config.skipping = None

    if renpy.config.after_replay_callback:
        renpy.config.after_replay_callback()
示例#3
0
def init():
    """
    Loads the persistent data from disk.

    This performs the initial load of persistent data from the local
    disk, so that we can configure the savelocation system.
    """

    filename = os.path.join(renpy.config.savedir, "persistent.new")
    persistent = load(filename)

    if persistent is None:
        filename = os.path.join(renpy.config.savedir, "persistent")
        persistent = load(filename)

    if persistent is None:
        persistent = Persistent()

    # Create the backup of the persistent data.
    v = vars(persistent)

    for k, v in six.iteritems(vars(persistent)):
        backup[k] = safe_deepcopy(v)

    return persistent
示例#4
0
def check_styles():
    for full_name, s in six.iteritems(renpy.style.styles):  # @UndefinedVariable
        name = "style." + full_name[0]
        for i in full_name[1:]:
            name += "[{!r}]".format(i)

        check_style("Style " + name, s)
示例#5
0
    def restore(self):
        """
        Restores the modules to a state similar to the state of the modules
        when the backup was created.
        """

        if not self.names:
            return

        # Remove new variables from the module.
        for mod, names in six.iteritems(self.names):
            modvars = vars(mod)
            for name in set(modvars.keys()) - names:
                del modvars[name]

        objects = cPickle.loads(self.objects_pickle)

        for k, v in six.iteritems(self.variables):
            mod, field = k
            setattr(mod, field, objects[v])
示例#6
0
    def pop_dynamic_roots(self, roots):

        for dynamic in reversed(self.dynamic_stack):

            for k, v in six.iteritems(dynamic):
                name = "store." + k

                if isinstance(v, Delete) and (name in roots):
                    del roots[name]
                else:
                    roots[name] = v
示例#7
0
def diff_memory(update=True):
    """
    :doc: memory

    Profiles objects, surface, and texture memory use by Ren'Py and the game.
    Writes (to memory.txt and stdout) the difference in memory usage from the
    last time this function was called with `update` true.

    The accounting is by names in the store and in the Ren'Py implementation
    that the memory is reachable from. If an object is reachable from more
    than one name, it's assigned to the name it's most directly reachable
    from.

    As it has to scan all memory used by Ren'Py, this function may take a
    long time to complete.
    """

    global old_usage
    global old_total

    write("=" * 78)
    write("")
    write("Memory diff at " + time.ctime() + ":")
    write("")

    usage = profile_memory_common()[0]
    total = sum(usage.values())

    diff = []

    for k, v in six.iteritems(usage):
        diff.append((v - old_usage.get(k, 0), k))

    diff.sort()

    for change, name in diff:
        if name == "renpy.memory.old_usage":
            continue

        if change:
            write("{:+14,d} {:13,d} {}".format(change, usage[name], name))

    write("-" * 14 + " " + "-" * 13)
    write("{:+14,d} {:13,d} {}".format(total - old_total, total,
                                       "Total memory usage (in bytes)."))
    write("")

    if update:
        old_usage = usage
        old_total = total
示例#8
0
def check_style(name, s):

    for p in s.properties:
        for k, v in six.iteritems(p):

            # Treat font specially.
            if k.endswith("font"):
                if isinstance(v, renpy.text.font.FontGroup):
                    for f in set(v.map.values()):
                        check_file(name, f)
                else:
                    check_file(name, v)

            if isinstance(v, renpy.display.core.Displayable):
                check_style_property_displayable(name, k, v)
示例#9
0
    def backup_module(self, mod):
        """
        Makes a backup of `mod`, which must be a Python module.
        """

        try:
            name = mod.__name__
        except:
            return

        if not name.startswith("renpy"):
            return

        if name in backup_blacklist:
            return

        if name.startswith("renpy.styledata"):
            return

        self.names[mod] = set(vars(mod).keys())

        for k, v in six.iteritems(vars(mod)):

            if k.startswith("__") and k.endswith("__"):
                continue

            if isinstance(v, type_blacklist):
                continue

            if name + "." + k in name_blacklist:
                continue

            idv = id(v)

            self.variables[mod, k] = idv
            self.objects[idv] = v

            # If we have a problem pickling things, uncomment the next block.

            try:
                cPickle.dumps(v, cPickle.HIGHEST_PROTOCOL)
            except:
                print("Cannot pickle", name + "." + k, "=", repr(v))
                print("Reduce Ex is:",
                      repr(v.__reduce_ex__(cPickle.HIGHEST_PROTOCOL)))
示例#10
0
    def pop_dynamic(self):
        """
        Pops one level of the dynamic stack. Called when the return
        statement is run.
        """

        if not self.dynamic_stack:
            return

        store = renpy.store.__dict__

        dynamic = self.dynamic_stack.pop()

        for k, v in six.iteritems(dynamic):
            if isinstance(v, Delete):
                store.pop(k, None)
            else:
                store[k] = v
示例#11
0
文件: easy.py 项目: susan-shu-c/renpy
def split_properties(properties, *prefixes):
    """
    :doc: other

    Splits up `properties` into multiple dictionaries, one per `prefix`. This
    function checks each key in properties against each prefix, in turn.
    When a prefix matches, the prefix is stripped from the key, and the
    resulting key is mapped to the value in the corresponding dictionary.

    If no prefix matches, an exception is thrown. (The empty string, "",
    can be used as the last prefix to create a catch-all dictionary.)

    For example, this splits properties beginning with text from
    those that do not::

        text_properties, button_properties = renpy.split_properties(properties, "text_", "")
    """

    rv = []

    for _i in prefixes:
        rv.append({})

    if not properties:
        return rv

    prefix_d = list(zip(prefixes, rv))

    for k, v in six.iteritems(properties):
        for prefix, d in prefix_d:
            if k.startswith(prefix):
                d[k[len(prefix):]] = v
                break
        else:
            raise Exception(
                "Property {} begins with an unknown prefix.".format(k))

    return rv
示例#12
0
    def visit(o, path):
        ido = id(o)

        if ido in seen:
            return

        seen.add(ido)

        if isinstance(o, (int, float, type(None), type)):
            return

        if isinstance(o, (tuple, list)):
            for i, oo in enumerate(o):
                rv = visit(oo, "{0}[{1!r}]".format(path, i))
                if rv is not None:
                    return rv

        elif isinstance(o, dict):
            for k, v in six.iteritems(o):
                rv = visit(v, "{0}[{1!r}]".format(path, k))
                if rv is not None:
                    return rv

        elif isinstance(o, types.MethodType):
            return visit(o.__self__, path + ".im_self")

        elif isinstance(o, types.ModuleType):

            return "{} = {}".format(path, repr(o)[:160])

        else:

            try:
                reduction = o.__reduce_ex__(2)
            except:

                import copy

                try:
                    copy.copy(o)
                    return None
                except:
                    pass

                return "{} = {}".format(path, repr(o)[:160])

            # Gets an element from the reduction, or o if we don't have
            # such an element.
            def get(idx, default):
                if idx < len(reduction) and reduction[idx] is not None:
                    return reduction[idx]
                else:
                    return default

            state = get(2, {})
            if isinstance(state, dict):
                for k, v in six.iteritems(state):
                    rv = visit(v, path + "." + k)
                    if rv is not None:
                        return rv
            else:
                rv = visit(state, path + ".__getstate__()")
                if rv is not None:
                    return rv

            for i, oo in enumerate(get(3, [])):
                rv = visit(oo, "{0}[{1}]".format(path, i))
                if rv is not None:
                    return rv

            for i in get(4, []):

                if len(i) != 2:
                    continue

                k, v = i

                rv = visit(v, "{0}[{1!r}]".format(path, k))
                if rv is not None:
                    return rv

        return None
示例#13
0
def profile_rollback():
    """
    :doc: memory

    Profiles memory used by the rollback system. Writes (to memory.txt and
    stdout) the memory used by the rollback system. This tries to account
    for rollback memory used by various store variables, as well as by
    internal aspects of the rollback system.
    """

    write("=" * 78)
    write("")
    write("Rollback profile at " + time.ctime() + ":")
    write("")

    # Profile live memory.
    seen = profile_memory_common(["store", "renpy.display"])[1]

    # Like seen, but for objects found in rollback.
    new_seen = {}

    log = list(renpy.game.log.log)
    log.reverse()

    roots = []

    # Walk the log, finding new roots and rollback information.
    for rb in log:

        for store_name, store in six.iteritems(rb.stores):
            for var_name, o in six.iteritems(store):
                name = store_name + "." + var_name
                id_o = id(o)

                if (id_o not in seen) and (id_o not in new_seen):
                    new_seen[id_o] = name

                roots.append((name, o))

        for o, roll in rb.objects:

            id_o = id(o)

            name = "<unknown>"
            name = new_seen.get(id_o, name)
            name = seen.get(id_o, name)

            roots.append((name, roll))

        roots.append(("<scene lists>", rb.context.scene_lists))
        roots.append(("<context>", rb.context))

    sizes = walk_memory(roots, seen)[0]

    usage = [(v, k) for (k, v) in six.iteritems(sizes)]
    usage.sort()

    write("Total Bytes".rjust(13) + " " + "Per Rollback".rjust(13))
    write("-" * 13 + " " + "-" * 13 + " " + "-" * 50)

    for size, name in usage:
        if name.startswith("renpy"):
            continue

        if size:
            write("{:13,d} {:13,d} {}".format(size, size // len(log), name))

    write("")
    write("{} Rollback objects exist.".format(len(log)))
    write("")
示例#14
0
    def visit(old_ido, o, path):
        ido = id(o)

        if old_ido is not None:
            edges.add((old_ido, ido, path))

        if ido in o_repr_cache:
            return

        paths[ido] = path

        if isinstance(o, (int, float, type(None), types.ModuleType, type)):
            o_repr = repr(o)

        elif isinstance(o, (str, six.text_type)):
            if len(o) <= 80:
                o_repr = repr(o).encode("utf-8")
            else:
                o_repr = repr(o[:80] + "...").encode("utf-8")

        elif isinstance(o, (tuple, list)):
            o_repr = "<" + o.__class__.__name__ + ">"

        elif isinstance(o, dict):
            o_repr = "<" + o.__class__.__name__ + ">"

        elif isinstance(o, types.MethodType):
            o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__,
                                               o.__func__.__name__)

        elif isinstance(o, object):
            o_repr = "<{0}>".format(type(o).__name__)

        else:
            o_repr = "BAD TYPE <{0}>".format(type(o).__name__)

        o_repr_cache[ido] = o_repr

        if isinstance(o, (tuple, list)):
            for i, oo in enumerate(o):
                visit(ido, oo, "{0}[{1!r}]".format(path, i))

        if isinstance(o, dict):
            for k, v in six.iteritems(o):
                visit(ido, v, "{0}[{1!r}]".format(path, k))

        elif isinstance(o, types.MethodType):
            visit(ido, o.__self__, path + ".im_self")

        else:

            try:
                reduction = o.__reduce_ex__(2)
            except:
                reduction = []

            # Gets an element from the reduction, or o if we don't have
            # such an element.
            def get(idx, default):
                if idx < len(reduction) and reduction[idx] is not None:
                    return reduction[idx]
                else:
                    return default

            state = get(2, {})
            if isinstance(state, dict):
                for k, v in six.iteritems(state):
                    visit(ido, v, path + "." + k)
            else:
                visit(ido, state, path + ".__getstate__()")

            for i, oo in enumerate(get(3, [])):
                visit(ido, oo, "{0}[{1}]".format(path, i))

            for i in get(4, []):

                if len(i) != 2:
                    continue

                k, v = i

                visit(ido, v, "{0}[{1!r}]".format(path, k))
示例#15
0
def dump(error):
    """
    Causes a JSON dump file to be written, if the user has requested it.

    `error`
        An error flag that is added to the written file.
    """

    global completed_dump

    args = renpy.game.args

    if completed_dump:
        return

    completed_dump = True

    if not args.json_dump:
        return

    def filter(name, filename):  # @ReservedAssignment
        """
        Returns true if the name is included by the filter, or false if it is excluded.
        """

        filename = filename.replace("\\", "/")

        if name.startswith("_") and not args.json_dump_private:
            if name.startswith("__") and name.endswith("__"):
                pass
            else:
                return False

        if not file_exists(filename):
            return False

        if filename.startswith("common/") or filename.startswith(
                "renpy/common/"):
            return args.json_dump_common

        if not filename.startswith("game/"):
            return False

        return True

    result = {}

    # Error flag.
    result["error"] = error

    # The size.
    result["size"] = [renpy.config.screen_width, renpy.config.screen_height]

    # The name and version.
    result["name"] = renpy.config.name
    result["version"] = renpy.config.version

    # The JSON object we return.
    location = {}
    result["location"] = location

    # Labels.
    label = location["label"] = {}

    for name, n in six.iteritems(renpy.game.script.namemap):
        filename = n.filename
        line = n.linenumber

        if not isinstance(name, six.string_types):
            continue

        if not filter(name, filename):
            continue

        label[name] = [filename, line]

    # Definitions.
    define = location["define"] = {}

    for name, filename, line in definitions:
        if not filter(name, filename):
            continue

        define[name] = [filename, line]

    # Screens.
    screen = location["screen"] = {}

    for name, filename, line in screens:
        if not filter(name, filename):
            continue

        screen[name] = [filename, line]

    # Transforms.
    transform = location["transform"] = {}

    for name, filename, line in transforms:
        if not filter(name, filename):
            continue

        transform[name] = [filename, line]

    # Code.

    def get_line(o):
        """
        Returns the filename and the first line number of the class or function o. Returns
        None, None if unknown.

        For a class, this doesn't return the first line number of the class, but rather
        the line number of the first method in the class - hopefully.
        """

        if inspect.isfunction(o):
            return inspect.getfile(o), o.__code__.co_firstlineno

        if inspect.ismethod(o):
            return get_line(o.__func__)

        return None, None

    code = location["callable"] = {}

    for modname, mod in sys.modules.items():

        if mod is None:
            continue

        if modname == "store":
            prefix = ""
        elif modname.startswith("store."):
            prefix = modname[6:] + "."
        else:
            continue

        for name, o in mod.__dict__.items():

            if inspect.isfunction(o):
                try:
                    if inspect.getmodule(o) != mod:
                        continue

                    filename, line = get_line(o)

                    if filename is None:
                        continue

                    if not filter(name, filename):
                        continue

                    code[prefix + name] = [filename, line]
                except:
                    continue

            if inspect.isclass(o):

                for methname, method in o.__dict__.iteritems():

                    try:
                        if inspect.getmodule(method) != mod:
                            continue

                        filename, line = get_line(method)

                        if filename is None:
                            continue

                        if not filter(name, filename):
                            continue

                        if not filter(methname, filename):
                            continue

                        code[prefix + name + "." + methname] = [filename, line]
                    except:
                        continue

    # Add the build info from 00build.rpy, if it's available.
    try:
        result["build"] = renpy.store.build.dump()  # @UndefinedVariable
    except:
        pass

    if args.json_dump != "-":
        new = args.json_dump + ".new"

        with open(new, "w") as f:
            json.dump(result, f)

        if os.path.exists(args.json_dump):
            os.unlink(args.json_dump)

        os.rename(new, args.json_dump)
    else:
        json.dump(result, sys.stdout, indent=2)
示例#16
0
    def visit(o, path):
        ido = id(o)

        if ido in o_repr_cache:
            f.write("{0: 7d} {1} = alias {2}\n".format(0, path,
                                                       o_repr_cache[ido]))
            return 0

        if isinstance(o, (int, float, type(None), types.ModuleType, type)):
            o_repr = repr(o)

        elif isinstance(o, (str, bytes, six.text_type)):
            if len(o) <= 80:
                o_repr = repr(o).encode("utf-8")
            else:
                o_repr = repr(o[:80] + "...").encode("utf-8")

        elif isinstance(o, (tuple, list)):
            o_repr = "<" + o.__class__.__name__ + ">"

        elif isinstance(o, dict):
            o_repr = "<" + o.__class__.__name__ + ">"

        elif isinstance(o, types.MethodType):
            o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__,
                                               o.__func__.__name__)

        elif isinstance(o, object):
            o_repr = "<{0}>".format(type(o).__name__)

        else:
            o_repr = "BAD TYPE <{0}>".format(type(o).__name__)

        o_repr_cache[ido] = o_repr

        if isinstance(o, (int, float, type(None), types.ModuleType, type)):
            size = 1

        elif isinstance(o, (str, six.text_type)):
            size = len(o) // 40 + 1

        elif isinstance(o, (tuple, list)):
            size = 1
            for i, oo in enumerate(o):
                size += 1
                size += visit(oo, "{0}[{1!r}]".format(path, i))

        elif isinstance(o, dict):
            size = 2
            for k, v in six.iteritems(o):
                size += 2
                size += visit(v, "{0}[{1!r}]".format(path, k))

        elif isinstance(o, types.MethodType):
            size = 1 + visit(o.__self__, path + ".im_self")

        else:

            try:
                reduction = o.__reduce_ex__(2)
            except:
                reduction = []
                o_repr = "BAD REDUCTION " + o_repr

            # Gets an element from the reduction, or o if we don't have
            # such an element.
            def get(idx, default):
                if idx < len(reduction) and reduction[idx] is not None:
                    return reduction[idx]
                else:
                    return default

            # An estimate of the size of the object, in arbitrary units. (These units are about 20-25 bytes on
            # my computer.)
            size = 1

            state = get(2, {})
            if isinstance(state, dict):
                for k, v in six.iteritems(state):
                    size += 2
                    size += visit(v, path + "." + k)
            else:
                size += visit(state, path + ".__getstate__()")

            for i, oo in enumerate(get(3, [])):
                size += 1
                size += visit(oo, "{0}[{1}]".format(path, i))

            for i in get(4, []):

                if len(i) != 2:
                    continue

                k, v = i

                size += 2
                size += visit(v, "{0}[{1!r}]".format(path, k))

        f.write("{0: 7d} {1} = {2}\n".format(size, path, o_repr_cache[ido]))

        return size
示例#17
0
def bootstrap(renpy_base):

    global renpy  # W0602

    import renpy.log  # @UnusedImport

    # Remove a legacy environment setting.
    if os.environ.get(b"SDL_VIDEODRIVER", "") == "windib":
        del os.environ[b"SDL_VIDEODRIVER"]

    renpy_base = six.text_type(renpy_base, FSENCODING, "replace")

    # If environment.txt exists, load it into the os.environ dictionary.
    if os.path.exists(renpy_base + "/environment.txt"):
        evars = {}
        with open(renpy_base + "/environment.txt", "rb") as f:
            code = compile(f.read(), renpy_base + "/environment.txt", 'exec')
            exec(code, evars)
        for k, v in six.iteritems(evars):
            if k not in os.environ:
                os.environ[k] = str(v)

    # Also look for it in an alternate path (the path that contains the
    # .app file.), if on a mac.
    alt_path = os.path.abspath("renpy_base")
    if ".app" in alt_path:
        alt_path = alt_path[:alt_path.find(".app") + 4]

        if os.path.exists(alt_path + "/environment.txt"):
            evars = {}
            with open(alt_path + "/environment.txt", "rb") as f:
                code = compile(f.read(), alt_path + "/environment.txt", 'exec')
                exec(code, evars)
            for k, v in six.iteritems(evars):
                if k not in os.environ:
                    os.environ[k] = str(v)

    # Get a working name for the game.
    name = os.path.basename(sys.argv[0])

    if name.find(".") != -1:
        name = name[:name.find(".")]

    # Parse the arguments.
    import renpy.arguments
    args = renpy.arguments.bootstrap()

    if args.trace:
        enable_trace(args.trace)

    if args.basedir:
        basedir = os.path.abspath(args.basedir).decode(FSENCODING)
    else:
        basedir = renpy_base

    if not os.path.exists(basedir):
        sys.stderr.write("Base directory %r does not exist. Giving up.\n" %
                         (basedir, ))
        sys.exit(1)

    gamedirs = [name]
    game_name = name

    while game_name:
        prefix = game_name[0]
        game_name = game_name[1:]

        if prefix == ' ' or prefix == '_':
            gamedirs.append(game_name)

    gamedirs.extend(['game', 'data', 'launcher/game'])

    for i in gamedirs:

        if i == "renpy":
            continue

        gamedir = basedir + "/" + i
        if os.path.isdir(gamedir):
            break
    else:
        gamedir = basedir

    sys.path.insert(0, basedir)

    if renpy.macintosh:
        # If we're on a mac, install our own os.start.
        os.startfile = mac_start

        # Are we starting from inside a mac app resources directory?
        if basedir.endswith("Contents/Resources/autorun"):
            renpy.macapp = True

    # Check that we have installed pygame properly. This also deals with
    # weird cases on Windows and Linux where we can't import modules. (On
    # windows ";" is a directory separator in PATH, so if it's in a parent
    # directory, we won't get the libraries in the PATH, and hence pygame
    # won't import.)
    try:
        import pygame_sdl2
        if not ("pygame" in sys.modules):
            pygame_sdl2.import_as_pygame()
    except:
        print("""\
Could not import pygame_sdl2. Please ensure that this program has been built
and unpacked properly. Also, make sure that the directories containing
this program do not contain : or ; in their names.

You may be using a system install of python. Please run {0}.sh,
{0}.exe, or {0}.app instead.
""".format(name),
              file=sys.stderr)

        raise

    # If we're not given a command, show the presplash.
    if args.command == "run" and not renpy.mobile:
        import renpy.display.presplash  # @Reimport
        renpy.display.presplash.start(basedir, gamedir)

    # Ditto for the Ren'Py module.
    try:
        import _renpy
        _renpy
    except:
        print("""\
Could not import _renpy. Please ensure that this program has been built
and unpacked properly.

You may be using a system install of python. Please run {0}.sh,
{0}.exe, or {0}.app instead.
""".format(name),
              file=sys.stderr)
        raise

    # Load up all of Ren'Py, in the right order.

    import renpy  # @Reimport
    renpy.import_all()

    renpy.loader.init_importer()

    exit_status = None

    try:
        while exit_status is None:
            exit_status = 1

            try:
                renpy.game.args = args
                renpy.config.renpy_base = renpy_base
                renpy.config.basedir = basedir
                renpy.config.gamedir = gamedir
                renpy.config.args = []

                if renpy.android:
                    renpy.config.logdir = os.environ['ANDROID_PUBLIC']
                else:
                    renpy.config.logdir = basedir

                if not os.path.exists(renpy.config.logdir):
                    os.makedirs(renpy.config.logdir, 0o777)

                renpy.main.main()

                exit_status = 0

            except KeyboardInterrupt:
                raise

            except renpy.game.UtterRestartException:

                # On an UtterRestart, reload Ren'Py.
                renpy.reload_all()

                exit_status = None

            except renpy.game.QuitException as e:
                exit_status = e.status

                if e.relaunch:
                    if hasattr(sys, "renpy_executable"):
                        subprocess.Popen([sys.renpy_executable] + sys.argv[1:])
                    else:
                        subprocess.Popen([sys.executable, "-EO"] + sys.argv)

            except renpy.game.ParseErrorException:
                pass

            except Exception as e:
                renpy.error.report_exception(e)
                pass

        sys.exit(exit_status)

    finally:

        if "RENPY_SHUTDOWN_TRACE" in os.environ:
            enable_trace(int(os.environ["RENPY_SHUTDOWN_TRACE"]))

        renpy.display.im.cache.quit()

        if renpy.display.draw:
            renpy.display.draw.quit()

        renpy.audio.audio.quit()

        # Prevent subprocess from throwing errors while trying to run it's
        # __del__ method during shutdown.
        if not renpy.emscripten:
            subprocess.Popen.__del__ = popen_del