From 718936110b9511631fa1f4396be992752bf8b719 Mon Sep 17 00:00:00 2001
From: Alex Xu <alex_y_xu@yahoo.ca>
Date: Thu, 22 Aug 2013 22:45:26 -0400
Subject: include renpy

---
 unrpyc/renpy/LICENSE.txt               | 1024 +++++++++++++
 unrpyc/renpy/README                    |    1 +
 unrpyc/renpy/__init__.py               |  278 ++++
 unrpyc/renpy/ast.py                    | 1757 +++++++++++++++++++++++
 unrpyc/renpy/atl.py                    | 1544 ++++++++++++++++++++
 unrpyc/renpy/display/__init__.py       |   39 +
 unrpyc/renpy/display/accelerator.pyx   |  296 ++++
 unrpyc/renpy/display/anim.py           |  634 ++++++++
 unrpyc/renpy/display/behavior.py       | 1531 ++++++++++++++++++++
 unrpyc/renpy/display/core.py           | 2463 ++++++++++++++++++++++++++++++++
 unrpyc/renpy/display/dragdrop.py       |  731 ++++++++++
 unrpyc/renpy/display/error.py          |  159 +++
 unrpyc/renpy/display/focus.py          |  449 ++++++
 unrpyc/renpy/display/im.py             | 1562 ++++++++++++++++++++
 unrpyc/renpy/display/image.py          |  397 +++++
 unrpyc/renpy/display/imagelike.py      |  382 +++++
 unrpyc/renpy/display/imagemap.py       |  233 +++
 unrpyc/renpy/display/joystick.py       |  126 ++
 unrpyc/renpy/display/layout.py         | 1744 ++++++++++++++++++++++
 unrpyc/renpy/display/minigame.py       |   25 +
 unrpyc/renpy/display/module.py         |  275 ++++
 unrpyc/renpy/display/motion.py         | 1526 ++++++++++++++++++++
 unrpyc/renpy/display/movetransition.py |  640 +++++++++
 unrpyc/renpy/display/particle.py       |  615 ++++++++
 unrpyc/renpy/display/pgrender.py       |  167 +++
 unrpyc/renpy/display/predict.py        |  156 ++
 unrpyc/renpy/display/presplash.py      |  113 ++
 unrpyc/renpy/display/render.pxd        |   47 +
 unrpyc/renpy/display/render.pyx        | 1174 +++++++++++++++
 unrpyc/renpy/display/scale.py          |  109 ++
 unrpyc/renpy/display/screen.py         |  639 +++++++++
 unrpyc/renpy/display/swdraw.py         | 1102 ++++++++++++++
 unrpyc/renpy/display/transition.py     |  922 ++++++++++++
 unrpyc/renpy/display/video.py          |  234 +++
 unrpyc/renpy/game.py                   |  437 ++++++
 unrpyc/renpy/log.py                    |  152 ++
 unrpyc/renpy/object.py                 |   60 +
 37 files changed, 23743 insertions(+)
 create mode 100644 unrpyc/renpy/LICENSE.txt
 create mode 100644 unrpyc/renpy/README
 create mode 100644 unrpyc/renpy/__init__.py
 create mode 100644 unrpyc/renpy/ast.py
 create mode 100644 unrpyc/renpy/atl.py
 create mode 100644 unrpyc/renpy/display/__init__.py
 create mode 100644 unrpyc/renpy/display/accelerator.pyx
 create mode 100644 unrpyc/renpy/display/anim.py
 create mode 100644 unrpyc/renpy/display/behavior.py
 create mode 100644 unrpyc/renpy/display/core.py
 create mode 100644 unrpyc/renpy/display/dragdrop.py
 create mode 100644 unrpyc/renpy/display/error.py
 create mode 100644 unrpyc/renpy/display/focus.py
 create mode 100644 unrpyc/renpy/display/im.py
 create mode 100644 unrpyc/renpy/display/image.py
 create mode 100644 unrpyc/renpy/display/imagelike.py
 create mode 100644 unrpyc/renpy/display/imagemap.py
 create mode 100644 unrpyc/renpy/display/joystick.py
 create mode 100644 unrpyc/renpy/display/layout.py
 create mode 100644 unrpyc/renpy/display/minigame.py
 create mode 100644 unrpyc/renpy/display/module.py
 create mode 100644 unrpyc/renpy/display/motion.py
 create mode 100644 unrpyc/renpy/display/movetransition.py
 create mode 100644 unrpyc/renpy/display/particle.py
 create mode 100644 unrpyc/renpy/display/pgrender.py
 create mode 100644 unrpyc/renpy/display/predict.py
 create mode 100644 unrpyc/renpy/display/presplash.py
 create mode 100644 unrpyc/renpy/display/render.pxd
 create mode 100644 unrpyc/renpy/display/render.pyx
 create mode 100644 unrpyc/renpy/display/scale.py
 create mode 100644 unrpyc/renpy/display/screen.py
 create mode 100644 unrpyc/renpy/display/swdraw.py
 create mode 100644 unrpyc/renpy/display/transition.py
 create mode 100644 unrpyc/renpy/display/video.py
 create mode 100644 unrpyc/renpy/game.py
 create mode 100644 unrpyc/renpy/log.py
 create mode 100644 unrpyc/renpy/object.py

(limited to 'unrpyc/renpy')

diff --git a/unrpyc/renpy/LICENSE.txt b/unrpyc/renpy/LICENSE.txt
new file mode 100644
index 0000000..383a780
--- /dev/null
+++ b/unrpyc/renpy/LICENSE.txt
@@ -0,0 +1,1024 @@
+.. highlight:: none
+
+=======
+License
+=======
+
+Most of Ren'Py is covered by the terms of the following (MIT) license:
+
+    Permission is hereby granted, free of charge, to any person
+    obtaining a copy of this software and associated documentation files
+    (the "Software"), to deal in the Software without restriction,
+    including without limitation the rights to use, copy, modify, merge,
+    publish, distribute, sublicense, and/or sell copies of the Software,
+    and to permit persons to whom the Software is furnished to do so,
+    subject to the following conditions:
+
+    The above copyright notice and this permission notice shall be
+    included in all copies or substantial portions of the Software.
+
+    THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+    EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+    MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+    NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+    LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+    OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+    WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+Portions of Ren'Py are derived from code that is copyright using the
+Lesser GNU Public License, so Ren'Py games must be distributed in a
+manner that satisfies the LGPL.
+
+Please see each individual source file for a list of copyright
+holders. The artwork in the demo is released by various copyright
+holders, under the same terms.
+
+Ren'Py binaries include code from the following projects:
+
+* Python (Python License)
+* Pygame (LGPL)
+* SDL (LGPL)
+* SDL_image (LGPL)
+* SDL_ttf (LGPL)
+* Freetype (LGPL)
+* Fribidi (LGPL)
+* libav (LGPL)
+* libjpeg-turbo (LGPL)
+* libpng (PNG license)
+* zlib (Zlib License)
+* bzip2 (Bzip2 License)
+* pyobjc (MIT License)
+* py2exe (MIT License)
+* GLEW (Modified BSD, MIT)
+* zsync (Artistic License)
+
+For the purpose of LGPL compliance, the source code to all LGPL
+software we depend on is either in the Ren'Py package (available from
+http://www.renpy.org/), or in the renpy-deps package
+(http://www.renpy.org/dl/lgpl/). We believe compliance can be achieved
+by including a copy of this license with every copy of Ren'Py you
+distribute, and referring to it in your project's README file.
+
+Ren'Py may be distributed alongside the jEdit or Editra text
+editors. Editra is licensed under the wxWindows license, while
+jEdit is under the GNU General Public License.
+
+
+GNU Lesser General Public License
+=================================
+
+::
+
+                   GNU LESSER GENERAL PUBLIC LICENSE
+                        Version 2.1, February 1999
+
+  Copyright (C) 1991, 1999 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.
+
+ [This is the first released version of the Lesser GPL.  It also counts
+  as the successor of the GNU Library Public License, version 2, hence
+  the version number 2.1.]
+
+                             Preamble
+
+   The licenses for most software are designed to take away your
+ freedom to share and change it.  By contrast, the GNU General Public
+ Licenses are intended to guarantee your freedom to share and change
+ free software--to make sure the software is free for all its users.
+
+   This license, the Lesser General Public License, applies to some
+ specially designated software packages--typically libraries--of the
+ Free Software Foundation and other authors who decide to use it.  You
+ can use it too, but we suggest you first think carefully about whether
+ this license or the ordinary General Public License is the better
+ strategy to use in any particular case, based on the explanations
+ below.
+
+   When we speak of free software, we are referring to freedom of use,
+ 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 and use pieces of
+ it in new free programs; and that you are informed that you can do
+ these things.
+
+   To protect your rights, we need to make restrictions that forbid
+ distributors to deny you these rights or to ask you to surrender these
+ rights.  These restrictions translate to certain responsibilities for
+ you if you distribute copies of the library or if you modify it.
+
+   For example, if you distribute copies of the library, whether gratis
+ or for a fee, you must give the recipients all the rights that we gave
+ you.  You must make sure that they, too, receive or can get the source
+ code.  If you link other code with the library, you must provide
+ complete object files to the recipients, so that they can relink them
+ with the library after making changes to the library and recompiling
+ it.  And you must show them these terms so they know their rights.
+
+   We protect your rights with a two-step method: (1) we copyright the
+ library, and (2) we offer you this license, which gives you legal
+ permission to copy, distribute and/or modify the library.
+
+   To protect each distributor, we want to make it very clear that
+ there is no warranty for the free library.  Also, if the library is
+ modified by someone else and passed on, the recipients should know
+ that what they have is not the original version, so that the original
+ author's reputation will not be affected by problems that might be
+ introduced by others.
+
+   Finally, software patents pose a constant threat to the existence of
+ any free program.  We wish to make sure that a company cannot
+ effectively restrict the users of a free program by obtaining a
+ restrictive license from a patent holder.  Therefore, we insist that
+ any patent license obtained for a version of the library must be
+ consistent with the full freedom of use specified in this license.
+
+   Most GNU software, including some libraries, is covered by the
+ ordinary GNU General Public License.  This license, the GNU Lesser
+ General Public License, applies to certain designated libraries, and
+ is quite different from the ordinary General Public License.  We use
+ this license for certain libraries in order to permit linking those
+ libraries into non-free programs.
+
+   When a program is linked with a library, whether statically or using
+ a shared library, the combination of the two is legally speaking a
+ combined work, a derivative of the original library.  The ordinary
+ General Public License therefore permits such linking only if the
+ entire combination fits its criteria of freedom.  The Lesser General
+ Public License permits more lax criteria for linking other code with
+ the library.
+
+   We call this license the "Lesser" General Public License because it
+ does Less to protect the user's freedom than the ordinary General
+ Public License.  It also provides other free software developers Less
+ of an advantage over competing non-free programs.  These disadvantages
+ are the reason we use the ordinary General Public License for many
+ libraries.  However, the Lesser license provides advantages in certain
+ special circumstances.
+
+   For example, on rare occasions, there may be a special need to
+ encourage the widest possible use of a certain library, so that it
+ becomes a de-facto standard.  To achieve this, non-free programs must
+ be allowed to use the library.  A more frequent case is that a free
+ library does the same job as widely used non-free libraries.  In this
+ case, there is little to gain by limiting the free library to free
+ software only, so we use the Lesser General Public License.
+
+   In other cases, permission to use a particular library in non-free
+ programs enables a greater number of people to use a large body of
+ free software.  For example, permission to use the GNU C Library in
+ non-free programs enables many more people to use the whole GNU
+ operating system, as well as its variant, the GNU/Linux operating
+ system.
+
+   Although the Lesser General Public License is Less protective of the
+ users' freedom, it does ensure that the user of a program that is
+ linked with the Library has the freedom and the wherewithal to run
+ that program using a modified version of the Library.
+
+   The precise terms and conditions for copying, distribution and
+ modification follow.  Pay close attention to the difference between a
+ "work based on the library" and a "work that uses the library".  The
+ former contains code derived from the library, whereas the latter must
+ be combined with the library in order to run.
+
+                   GNU LESSER GENERAL PUBLIC LICENSE
+    TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
+
+   0. This License Agreement applies to any software library or other
+ program which contains a notice placed by the copyright holder or
+ other authorized party saying it may be distributed under the terms of
+ this Lesser General Public License (also called "this License").
+ Each licensee is addressed as "you".
+
+   A "library" means a collection of software functions and/or data
+ prepared so as to be conveniently linked with application programs
+ (which use some of those functions and data) to form executables.
+
+   The "Library", below, refers to any such software library or work
+ which has been distributed under these terms.  A "work based on the
+ Library" means either the Library or any derivative work under
+ copyright law: that is to say, a work containing the Library or a
+ portion of it, either verbatim or with modifications and/or translated
+ straightforwardly into another language.  (Hereinafter, translation is
+ included without limitation in the term "modification".)
+
+   "Source code" for a work means the preferred form of the work for
+ making modifications to it.  For a library, 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 library.
+
+   Activities other than copying, distribution and modification are not
+ covered by this License; they are outside its scope.  The act of
+ running a program using the Library is not restricted, and output from
+ such a program is covered only if its contents constitute a work based
+ on the Library (independent of the use of the Library in a tool for
+ writing it).  Whether that is true depends on what the Library does
+ and what the program that uses the Library does.
+
+   1. You may copy and distribute verbatim copies of the Library's
+ complete 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 distribute a copy of this License along with the
+ Library.
+
+   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 Library or any portion
+ of it, thus forming a work based on the Library, 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) The modified work must itself be a software library.
+
+     b) You must cause the files modified to carry prominent notices
+     stating that you changed the files and the date of any change.
+
+     c) You must cause the whole of the work to be licensed at no
+     charge to all third parties under the terms of this License.
+
+     d) If a facility in the modified Library refers to a function or a
+     table of data to be supplied by an application program that uses
+     the facility, other than as an argument passed when the facility
+     is invoked, then you must make a good faith effort to ensure that,
+     in the event an application does not supply such function or
+     table, the facility still operates, and performs whatever part of
+     its purpose remains meaningful.
+
+     (For example, a function in a library to compute square roots has
+     a purpose that is entirely well-defined independent of the
+     application.  Therefore, Subsection 2d requires that any
+     application-supplied function or table used by this function must
+     be optional: if the application does not supply it, the square
+     root function must still compute square roots.)
+
+ These requirements apply to the modified work as a whole.  If
+ identifiable sections of that work are not derived from the Library,
+ 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 Library, 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 Library.
+
+ In addition, mere aggregation of another work not based on the Library
+ with the Library (or with a work based on the Library) on a volume of
+ a storage or distribution medium does not bring the other work under
+ the scope of this License.
+
+   3. You may opt to apply the terms of the ordinary GNU General Public
+ License instead of this License to a given copy of the Library.  To do
+ this, you must alter all the notices that refer to this License, so
+ that they refer to the ordinary GNU General Public License, version 2,
+ instead of to this License.  (If a newer version than version 2 of the
+ ordinary GNU General Public License has appeared, then you can specify
+ that version instead if you wish.)  Do not make any other change in
+ these notices.
+
+   Once this change is made in a given copy, it is irreversible for
+ that copy, so the ordinary GNU General Public License applies to all
+ subsequent copies and derivative works made from that copy.
+
+   This option is useful when you wish to copy part of the code of
+ the Library into a program that is not a library.
+
+   4. You may copy and distribute the Library (or a portion or
+ derivative of it, under Section 2) in object code or executable form
+ under the terms of Sections 1 and 2 above provided that you 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.
+
+   If distribution of 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 satisfies the requirement to
+ distribute the source code, even though third parties are not
+ compelled to copy the source along with the object code.
+
+   5. A program that contains no derivative of any portion of the
+ Library, but is designed to work with the Library by being compiled or
+ linked with it, is called a "work that uses the Library".  Such a
+ work, in isolation, is not a derivative work of the Library, and
+ therefore falls outside the scope of this License.
+
+   However, linking a "work that uses the Library" with the Library
+ creates an executable that is a derivative of the Library (because it
+ contains portions of the Library), rather than a "work that uses the
+ library".  The executable is therefore covered by this License.
+ Section 6 states terms for distribution of such executables.
+
+   When a "work that uses the Library" uses material from a header file
+ that is part of the Library, the object code for the work may be a
+ derivative work of the Library even though the source code is not.
+ Whether this is true is especially significant if the work can be
+ linked without the Library, or if the work is itself a library.  The
+ threshold for this to be true is not precisely defined by law.
+
+   If such an object file uses only numerical parameters, data
+ structure layouts and accessors, and small macros and small inline
+ functions (ten lines or less in length), then the use of the object
+ file is unrestricted, regardless of whether it is legally a derivative
+ work.  (Executables containing this object code plus portions of the
+ Library will still fall under Section 6.)
+
+   Otherwise, if the work is a derivative of the Library, you may
+ distribute the object code for the work under the terms of Section 6.
+ Any executables containing that work also fall under Section 6,
+ whether or not they are linked directly with the Library itself.
+
+   6. As an exception to the Sections above, you may also combine or
+ link a "work that uses the Library" with the Library to produce a
+ work containing portions of the Library, and distribute that work
+ under terms of your choice, provided that the terms permit
+ modification of the work for the customer's own use and reverse
+ engineering for debugging such modifications.
+
+   You must give prominent notice with each copy of the work that the
+ Library is used in it and that the Library and its use are covered by
+ this License.  You must supply a copy of this License.  If the work
+ during execution displays copyright notices, you must include the
+ copyright notice for the Library among them, as well as a reference
+ directing the user to the copy of this License.  Also, you must do one
+ of these things:
+
+     a) Accompany the work with the complete corresponding
+     machine-readable source code for the Library including whatever
+     changes were used in the work (which must be distributed under
+     Sections 1 and 2 above); and, if the work is an executable linked
+     with the Library, with the complete machine-readable "work that
+     uses the Library", as object code and/or source code, so that the
+     user can modify the Library and then relink to produce a modified
+     executable containing the modified Library.  (It is understood
+     that the user who changes the contents of definitions files in the
+     Library will not necessarily be able to recompile the application
+     to use the modified definitions.)
+
+     b) Use a suitable shared library mechanism for linking with the
+     Library.  A suitable mechanism is one that (1) uses at run time a
+     copy of the library already present on the user's computer system,
+     rather than copying library functions into the executable, and (2)
+     will operate properly with a modified version of the library, if
+     the user installs one, as long as the modified version is
+     interface-compatible with the version that the work was made with.
+
+     c) Accompany the work with a written offer, valid for at least
+     three years, to give the same user the materials specified in
+     Subsection 6a, above, for a charge no more than the cost of
+     performing this distribution.
+
+     d) If distribution of the work is made by offering access to copy
+     from a designated place, offer equivalent access to copy the above
+     specified materials from the same place.
+
+     e) Verify that the user has already received a copy of these
+     materials or that you have already sent this user a copy.
+
+   For an executable, the required form of the "work that uses the
+ Library" must include any data and utility programs needed for
+ reproducing the executable from it.  However, as a special exception,
+ the materials to be 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.
+
+   It may happen that this requirement contradicts the license
+ restrictions of other proprietary libraries that do not normally
+ accompany the operating system.  Such a contradiction means you cannot
+ use both them and the Library together in an executable that you
+ distribute.
+
+   7. You may place library facilities that are a work based on the
+ Library side-by-side in a single library together with other library
+ facilities not covered by this License, and distribute such a combined
+ library, provided that the separate distribution of the work based on
+ the Library and of the other library facilities is otherwise
+ permitted, and provided that you do these two things:
+
+     a) Accompany the combined library with a copy of the same work
+     based on the Library, uncombined with any other library
+     facilities.  This must be distributed under the terms of the
+     Sections above.
+
+     b) Give prominent notice with the combined library of the fact
+     that part of it is a work based on the Library, and explaining
+     where to find the accompanying uncombined form of the same work.
+
+   8. You may not copy, modify, sublicense, link with, or distribute
+ the Library except as expressly provided under this License.  Any
+ attempt otherwise to copy, modify, sublicense, link with, or
+ distribute the Library 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.
+
+   9. 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 Library or its derivative works.  These actions are
+ prohibited by law if you do not accept this License.  Therefore, by
+ modifying or distributing the Library (or any work based on the
+ Library), you indicate your acceptance of this License to do so, and
+ all its terms and conditions for copying, distributing or modifying
+ the Library or works based on it.
+
+   10. Each time you redistribute the Library (or any work based on the
+ Library), the recipient automatically receives a license from the
+ original licensor to copy, distribute, link with or modify the Library
+ 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 with
+ this License.
+
+   11. 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 Library at all.  For example, if a patent
+ license would not permit royalty-free redistribution of the Library 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 Library.
+
+ 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.
+
+   12. If the distribution and/or use of the Library is restricted in
+ certain countries either by patents or by copyrighted interfaces, the
+ original copyright holder who places the Library 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.
+
+   13. The Free Software Foundation may publish revised and/or new
+ versions of the Lesser 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 Library
+ 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 Library does not specify a
+ license version number, you may choose any version ever published by
+ the Free Software Foundation.
+
+   14. If you wish to incorporate parts of the Library into other free
+ programs whose distribution conditions are incompatible with these,
+ 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
+
+   15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO
+ WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW.
+ EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR
+ OTHER PARTIES PROVIDE THE LIBRARY "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
+ LIBRARY IS WITH YOU.  SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME
+ THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+   16. 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 LIBRARY 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
+ LIBRARY (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 LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), 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 Libraries
+
+   If you develop a new library, and you want it to be of the greatest
+ possible use to the public, we recommend making it free software that
+ everyone can redistribute and change.  You can do so by permitting
+ redistribution under these terms (or, alternatively, under the terms
+ of the ordinary General Public License).
+
+   To apply these terms, attach the following notices to the library.
+ 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.
+
+
+     <one line to give the library's name and a brief idea of what it does.>
+     Copyright (C) <year>  <name of author>
+
+     This library is free software; you can redistribute it and/or
+     modify it under the terms of the GNU Lesser General Public
+     License as published by the Free Software Foundation; either
+     version 2.1 of the License, or (at your option) any later version.
+
+     This library 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
+     Lesser General Public License for more details.
+
+     You should have received a copy of the GNU Lesser General Public
+     License along with this library; 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.
+
+ You should also get your employer (if you work as a programmer) or
+ your school, if any, to sign a "copyright disclaimer" for the library,
+ if necessary.  Here is a sample; alter the names:
+
+   Yoyodyne, Inc., hereby disclaims all copyright interest in the
+   library `Frob' (a library for tweaking knobs) written by James
+   Random Hacker.
+
+   <signature of Ty Coon>, 1 April 1990
+   Ty Coon, President of Vice
+
+ That's all there is to it!
+
+
+Python License
+==============
+
+::
+
+ 1. This LICENSE AGREEMENT is between the Python Software Foundation
+ ("PSF"), and the Individual or Organization ("Licensee") accessing and
+ otherwise using Python 2.3 software in source or binary form and its
+ associated documentation.
+
+ 2. Subject to the terms and conditions of this License Agreement, PSF
+ hereby grants Licensee a nonexclusive, royalty-free, world-wide
+ license to reproduce, analyze, test, perform and/or display publicly,
+ prepare derivative works, distribute, and otherwise use Python 2.3
+ alone or in any derivative version, provided, however, that PSF's
+ License Agreement and PSF's notice of copyright, i.e., "Copyright (c)
+ 2001, 2002 Python Software Foundation; All Rights Reserved" are
+ retained in Python 2.3 alone or in any derivative version prepared by
+ Licensee.
+
+ 3. In the event Licensee prepares a derivative work that is based on
+ or incorporates Python 2.3 or any part thereof, and wants to make
+ the derivative work available to others as provided herein, then
+ Licensee hereby agrees to include in any such work a brief summary of
+ the changes made to Python 2.3.
+
+ 4. PSF is making Python 2.3 available to Licensee on an "AS IS"
+ basis.  PSF MAKES NO REPRESENTATIONS OR WARRANTIES, EXPRESS OR
+ IMPLIED.  BY WAY OF EXAMPLE, BUT NOT LIMITATION, PSF MAKES NO AND
+ DISCLAIMS ANY REPRESENTATION OR WARRANTY OF MERCHANTABILITY OR FITNESS
+ FOR ANY PARTICULAR PURPOSE OR THAT THE USE OF PYTHON 2.3 WILL NOT
+ INFRINGE ANY THIRD PARTY RIGHTS.
+
+ 5. PSF SHALL NOT BE LIABLE TO LICENSEE OR ANY OTHER USERS OF PYTHON
+ 2.3 FOR ANY INCIDENTAL, SPECIAL, OR CONSEQUENTIAL DAMAGES OR LOSS AS
+ A RESULT OF MODIFYING, DISTRIBUTING, OR OTHERWISE USING PYTHON 2.3,
+ OR ANY DERIVATIVE THEREOF, EVEN IF ADVISED OF THE POSSIBILITY THEREOF.
+
+ 6. This License Agreement will automatically terminate upon a material
+ breach of its terms and conditions.
+
+ 7. Nothing in this License Agreement shall be deemed to create any
+ relationship of agency, partnership, or joint venture between PSF and
+ Licensee.  This License Agreement does not grant permission to use PSF
+ trademarks or trade name in a trademark sense to endorse or promote
+ products or services of Licensee, or any third party.
+
+ 8. By copying, installing or otherwise using Python 2.3, Licensee
+ agrees to be bound by the terms and conditions of this License
+ Agreement.
+
+Jpeg License
+============
+
+::
+
+ In plain English:
+
+ 1. We don't promise that this software works.  (But if you find any bugs,
+    please let us know!)
+ 2. You can use this software for whatever you want.  You don't have to pay us.
+ 3. You may not pretend that you wrote this software.  If you use it in a
+    program, you must acknowledge somewhere in your documentation that
+    you've used the IJG code.
+
+ In legalese:
+
+ The authors make NO WARRANTY or representation, either express or implied,
+ with respect to this software, its quality, accuracy, merchantability, or
+ fitness for a particular purpose.  This software is provided "AS IS", and you,
+ its user, assume the entire risk as to its quality and accuracy.
+
+ This software is copyright (C) 1991-1998, Thomas G. Lane.
+ All Rights Reserved except as specified below.
+
+ Permission is hereby granted to use, copy, modify, and distribute this
+ software (or portions thereof) for any purpose, without fee, subject to these
+ conditions:
+ (1) If any part of the source code for this software is distributed, then this
+ README file must be included, with this copyright and no-warranty notice
+ unaltered; and any additions, deletions, or changes to the original files
+ must be clearly indicated in accompanying documentation.
+ (2) If only executable code is distributed, then the accompanying
+ documentation must state that "this software is based in part on the work of
+ the Independent JPEG Group".
+ (3) Permission for use of this software is granted only if the user accepts
+ full responsibility for any undesirable consequences; the authors accept
+ NO LIABILITY for damages of any kind.
+
+ These conditions apply to any software derived from or based on the IJG code,
+ not just to the unmodified library.  If you use our work, you ought to
+ acknowledge us.
+
+ Permission is NOT granted for the use of any IJG author's name or company name
+ in advertising or publicity relating to this software or products derived from
+ it.  This software may be referred to only as "the Independent JPEG Group's
+ software".
+
+ We specifically permit and encourage the use of this software as the basis of
+ commercial products, provided that all warranty or liability claims are
+ assumed by the product vendor.
+
+
+PNG License
+===========
+
+::
+
+ The PNG Reference Library is supplied "AS IS".  The Contributing Authors
+ and Group 42, Inc. disclaim all warranties, expressed or implied,
+ including, without limitation, the warranties of merchantability and of
+ fitness for any purpose.  The Contributing Authors and Group 42, Inc.
+ assume no liability for direct, indirect, incidental, special, exemplary,
+ or consequential damages, which may result from the use of the PNG
+ Reference Library, even if advised of the possibility of such damage.
+
+ Permission is hereby granted to use, copy, modify, and distribute this
+ source code, or portions hereof, for any purpose, without fee, subject
+ to the following restrictions:
+
+ 1. The origin of this source code must not be misrepresented.
+
+ 2. Altered versions must be plainly marked as such and must not
+    be misrepresented as being the original source.
+
+ 3. This Copyright notice may not be removed or altered from any
+    source or altered source distribution.
+
+ The Contributing Authors and Group 42, Inc. specifically permit, without
+ fee, and encourage the use of this source code as a component to
+ supporting the PNG file format in commercial products.  If you use this
+ source code in a product, acknowledgment is not required but would be
+ appreciated.
+
+Zlib License
+============
+
+::
+
+  This software is provided 'as-is', without any express or implied
+  warranty.  In no event will the authors be held liable for any damages
+  arising from the use of this software.
+
+  Permission is granted to anyone to use this software for any purpose,
+  including commercial applications, and to alter it and redistribute it
+  freely, subject to the following restrictions:
+
+  1. The origin of this software must not be misrepresented; you must not
+     claim that you wrote the original software. If you use this software
+     in a product, an acknowledgment in the product documentation would be
+     appreciated but is not required.
+  2. Altered source versions must be plainly marked as such, and must not be
+     misrepresented as being the original software.
+  3. This notice may not be removed or altered from any source distribution.
+
+
+Bzip2 License
+=============
+
+::
+
+ This program, "bzip2", the associated library "libbzip2", and all
+ documentation, are copyright (C) 1996-2005 Julian R Seward.  All
+ rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+
+ 2. The origin of this software must not be misrepresented; you must 
+    not claim that you wrote the original software.  If you use this 
+    software in a product, an acknowledgment in the product 
+    documentation would be appreciated but is not required.
+
+ 3. Altered source versions must be plainly marked as such, and must
+    not be misrepresented as being the original software.
+
+ 4. The name of the author may not be used to endorse or promote 
+    products derived from this software without specific prior written 
+    permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE AUTHOR ''AS IS'' AND ANY EXPRESS
+ OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY
+ DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
+ GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+ WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Modified BSD License
+====================
+
+::
+
+ Redistribution and use in source and binary forms, with or without 
+ modification, are permitted provided that the following conditions are met:
+
+ * Redistributions of source code must retain the above copyright notice, 
+   this list of conditions and the following disclaimer.
+ * Redistributions in binary form must reproduce the above copyright notice, 
+   this list of conditions and the following disclaimer in the documentation 
+   and/or other materials provided with the distribution.
+ * The name of the author may be used to endorse or promote products 
+   derived from this software without specific prior written permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 
+ AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 
+ IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE 
+ LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR 
+ CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF 
+ SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
+ THE POSSIBILITY OF SUCH DAMAGE.
+
+
+Artistic License
+================
+
+::
+
+                              The Artistic License
+                          Version 2.0beta4, October 2000
+
+                          Copyright (C) 2000, Larry Wall.
+         Everyone is permitted to copy and distribute verbatim copies
+          of this license document, but changing it is not allowed.
+
+                                   Preamble
+
+ This copyright license states the terms under which a given free
+ software Package may be copied, modified and/or redistributed, while the
+ Originator(s) maintain some artistic control over the future development
+ of that Package (at least as much artistic control as can be given under
+ copyright law while still making the Package open source and free software).
+
+ This license is bound by copyright law, and thus it legally applies only
+ to works which the copyright holder has permitted copying, distribution
+ or modification under the terms of the Artistic License, Version 2.0.
+
+ You are reminded that You are always permitted to make arrangements
+ wholly outside of a given copyright license directly with the copyright
+ holder(s) of a given Package. If the terms of this license impede your
+ ability to make full use of the Package, You are encouraged to contact
+ the copyright holder(s) and seek a different licensing arrangement.
+
+                                 Definitions
+
+ "Package" refers to the collection of files distributed by the
+ Originator(s), and derivatives of that collection of files created
+ through textual modification.
+
+ "Standard Version" refers to the Package if it has not been modified, or
+ has been modified only in ways suggested by the Originator(s).
+
+ "Modified Version" refers to the Package, if it has been changed by You
+ via textual modification of the source code, and such changes were not
+ suggested by the Originator(s).
+
+ "Originator" refers to the author(s) and/or copyright holder(s) of the
+ Standard Version of the Package.
+
+ "You" and "Your" refers to any person who would like to copy,
+ distribute, or modify the Package.
+
+ "Distribution Fee" is any fee that You charge for providing a copy of
+ this Package to another party. It does not refer to licensing fees.
+
+ "Freely Available" means that:
+
+        (a) no fee is charged for the right to use the item (though a
+            Distribution Fee may be charged).
+
+        (b) recipients of the item may redistribute it under the same
+            conditions they received it.
+
+        (c) If the item is a binary, object code, bytecode, the complete
+            corresponding machine-readable source code is included with the
+            item.
+
+
+          Permission for Use and Modification Without Redistribution
+
+   (1) You are permitted to use the Standard Version and create and use
+       Modified Versions for any purpose without restriction, provided that
+       you do not redistribute the Modified Version to others outside of your
+       company or organization.
+
+
+            Permissions for Redistribution of the Standard Version
+
+   (2) You may make available verbatim copies of the source code of the
+       Standard Version of this Package in any medium without restriction,
+       either gratis or for a Distribution Fee, provided that you duplicate
+       all of the original copyright notices and associated disclaimers.  At
+       Your discretion, such verbatim copies may or may not include compiled
+       bytecode, object code or binary versions of the corresponding source
+       code in the same medium.
+
+   (3) You may apply any bug fixes, portability changes, and other
+       modifications made available from any of the Originator(s).  The
+       resulting modified Package will still be considered the Standard
+       Version, and may be copied, modified and redistributed under the terms
+       of the original license of the Standard Version as if it were the
+       Standard Version.
+
+
+  Permissions for Redistribution of Modified Versions of the Package as Source
+
+   (4) You may modify your copy of the source code of this Package in any way
+       and distribute that Modified Version (either gratis or for a
+       Distribution Fee, and with or without a corresponding binary, bytecode
+       or object code version of the Modified Version) provided that You
+       clearly indicate what changes You made to the Package, and provided
+       that You do at least ONE of the following:
+
+        (a) make the Modified Version available to the Originator(s) of the
+            Standard Version, under the exact license of the Standard
+            Version, so that the Originator(s) may include your modifications
+            into the Standard Version (at their discretion).
+
+        (b) modify any installation scripts and procedures so that
+            installation of the Modified Version will never conflict with an
+            installation of the Standard Version, include for each program
+            installed by the Modified Version clear documentation describing
+            how it differs from the Standard Version, and rename your
+            Modified Version so that the name is substantially different from
+            the Standard Version.
+
+        (c) permit and encourage anyone who receives a copy of the Modified
+            Version permission to make your modifications Freely Available in
+            some specific way.                                               
+
+       If Your Modified Version is in turn derived from a Modified Version
+       made by a third party, then You are still required to ensure that Your
+       Modified Version complies with the requirements of this license.
+
+
+       Permissions for Redistribution of Non-Source Versions of Package
+
+   (5) You may distribute binary, object code, bytecode or other non-source
+       versions of the Standard Version of the Package, provided that you
+       include complete instructions on where to get the source code of the
+       Standard Version.  Such instructions must be valid at the time of Your
+       distribution.  If these instructions, at any time while You are
+       carrying our such distribution, become invalid, you must provide new
+       instructions on demand or cease further distribution.  If You cease
+       distribution within thirty days after You become aware that the
+       instructions are invalid, then You do not forfeit any of Your rights
+       under this license.
+
+   (6) You may distribute binary, object code, bytecode or other non-source
+       versions of a Modified Version provided that You do at least ONE of
+       the following:
+
+        (a) include a copy of the corresponding source code for the Modified
+            Version under the terms indicated in (4).
+
+        (b) ensure that the installation of Your non-source Modified Version
+            does not conflict in any way with an installation of the Standard
+            Version, include for each program installed by the Modified
+            Version clear documentation describing how it differs from the
+            Standard Version, and rename your Modified Version so that the
+            name is substantially different from the Standard Version.
+
+        (c) ensure that the Modified Version includes notification of the
+            changes made from the Standard Version, and offer to provide
+            machine-readable source code (under a license that permits making
+            that source code Freely Available) of the Modified Version via
+            mail order.
+
+
+         Permissions for Inclusion of the Package in Aggregate Works
+
+   (7) You may aggregate this Package (either the Standard Version or
+       Modified Version) with other packages and distribute the resulting
+       aggregation provided that You do not charge a licensing fee for the
+       Package.  Distribution Fees are permitted, and licensing fees for
+       other packages in the aggregation are permitted.  Your permission to
+       distribute Standard or Modified Versions of the Package is still
+       subject to the other terms set forth in other sections of this
+       license.
+
+   (8) In addition to the permissions given elsewhere by this license, You
+       are also permitted to link Modified and Standard Versions of this
+       Package with other works and distribute the result without
+       restriction, provided You have produced binary program(s) that do not
+       overtly expose the interfaces of the Package.  This includes
+       permission to embed the Package in a larger work of your own without
+       exposing a direct interface to the Package.  This also includes
+       permission to build stand-alone binary or bytecode versions of your
+       scripts that require the Package, but do not otherwise give the casual
+       user direct access to the Package itself.
+
+
+         Items That are Never Considered Part of a Modified Version Package
+
+   (9) Works (including, but not limited to, subroutines and scripts) that
+       you have linked or aggregated with the Package that merely extend or
+       make use of the Package, but are not intended to cause the Package to
+       operate differently from the Standard Version, do not, by themselves,
+       cause the Package to be a Modified Version.  In addition, such works
+       are not considered parts of the Package itself, and are not bound by
+       the terms of the Package's license.
+
+
+               Acceptance of License and Disclaimer of Warranty
+
+  (10) You are not required to accept this License, since you have not signed
+       it.  However, nothing else grants you permission to copy, modify or
+       distribute the Standard or Modified Versions of the Package.  These
+       actions are prohibited by copyright law if you do not accept this
+       License.  Therefore, by copying, modifying or distributing Standard
+       and Modified Versions of the Package, you indicate your acceptance of
+       the license of the Package.
+
+
+  (11) Disclaimer of Warranty:
+
+        THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+        "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+        LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+        A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT UNLESS REQUIRED BY
+        LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER OR CONTRIBUTOR
+        BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY,
+        OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT
+        OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, PROFITS; OR
+        BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
+        WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
+        OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE,
+        EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
diff --git a/unrpyc/renpy/README b/unrpyc/renpy/README
new file mode 100644
index 0000000..0e04cdc
--- /dev/null
+++ b/unrpyc/renpy/README
@@ -0,0 +1 @@
+This is Ren'Py 6.15.4 with significant deletions (to minimize size).
diff --git a/unrpyc/renpy/__init__.py b/unrpyc/renpy/__init__.py
new file mode 100644
index 0000000..443cb49
--- /dev/null
+++ b/unrpyc/renpy/__init__.py
@@ -0,0 +1,278 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file ensures that renpy packages will be imported in the right
+# order.
+
+import sys
+import os
+
+# Version numbers.
+try:
+    from renpy.vc_version import vc_version; vc_version
+except ImportError:
+    vc_version = 0
+
+# The tuple giving the version. This needs to be updated when
+# we bump the version. 
+#
+# Be sure to change script_version in launcher/script_version.rpy.
+# Be sure to change config.version in tutorial/game/options.rpy.
+version_tuple = (6, 15, 4, vc_version)
+
+# A verbose string computed from that version.
+version = "Ren'Py " + ".".join(str(i) for i in version_tuple)
+
+# Other versions.
+script_version = 5003000
+savegame_suffix = "-LT1.save"
+bytecode_version = 1
+
+# True if this is the first time we've started - even including
+# utter restarts.
+first_utter_start = True
+
+def setup_modulefinder(modulefinder):
+    import _renpy 
+
+    libexec = os.path.dirname(_renpy.__file__)
+
+    for i in [ "display", "gl", "angle", "text" ]:
+    
+        displaypath = os.path.join(libexec, "renpy", i)
+
+        if os.path.exists(displaypath):
+            modulefinder.AddPackagePath('renpy.' + i, displaypath)
+
+def import_cython():
+    """
+    Never called, but necessary to ensure that modulefinder will properly
+    grab the various cython modules.
+    """
+
+    import renpy.arguments #@UnresolvedImport
+
+    import renpy.display.accelerator #@UnresolvedImport
+    import renpy.display.render #@UnresolvedImport
+
+    import renpy.gl.gldraw #@UnresolvedImport
+    import renpy.gl.glenviron_fixed #@UnresolvedImport
+    import renpy.gl.glenviron_limited #@UnresolvedImport
+    import renpy.gl.glenviron_shader #@UnresolvedImport
+    import renpy.gl.glrtt_copy #@UnresolvedImport
+    import renpy.gl.glrtt_fbo #@UnresolvedImport
+    import renpy.gl.gltexture #@UnresolvedImport
+
+    import renpy.angle.gldraw #@UnresolvedImport
+    import renpy.angle.glenviron_shader #@UnresolvedImport
+    import renpy.angle.glrtt_copy #@UnresolvedImport
+    import renpy.angle.glrtt_fbo #@UnresolvedImport
+    import renpy.angle.gltexture #@UnresolvedImport
+    
+    
+def import_all():
+
+    import renpy.log #@UnresolvedImport
+    
+    import renpy.display #@UnresolvedImport
+
+    # Should probably be early, as we will add it as a base to serialized things.
+    import renpy.object #@UnresolvedImport
+
+    import renpy.game #@UnresolvedImport
+
+    # Adds in the Ren'Py loader.
+    import renpy.loader #@UnresolvedImport
+
+    import renpy.ast #@UnresolvedImport
+    import renpy.atl #@UnresolvedImport
+    import renpy.curry #@UnresolvedImport
+    import renpy.easy #@UnresolvedImport
+    import renpy.execution #@UnresolvedImport
+    import renpy.loadsave #@UnresolvedImport
+    import renpy.parser #@UnresolvedImport
+    import renpy.python #@UnresolvedImport
+    import renpy.script #@UnresolvedImport
+    import renpy.statements #@UnresolvedImport
+    import renpy.style #@UnresolvedImport
+    import renpy.substitutions #@UnresolvedImport
+    import renpy.translation #@UnresolvedImport
+
+    import renpy.display.presplash #@UnresolvedImport
+    import renpy.display.pgrender #@UnresolvedImport
+    import renpy.display.scale #@UnresolvedImport
+    import renpy.display.module #@UnresolvedImport
+
+    def update_path(package):
+        """
+        Update the __path__ of package, to import binary modules from a libexec
+        directory.
+        """
+        
+        name = package.__name__.split(".")
+        
+        import _renpy #@UnresolvedImport
+        libexec = os.path.dirname(_renpy.__file__)
+        package.__path__.append(os.path.join(libexec, *name))
+    
+        # Also find encodings, to deal with the way py2exe lays things out.
+        import encodings
+        libexec = os.path.dirname(encodings.__path__[0])
+        package.__path__.append(os.path.join(libexec, *name))
+
+    update_path(renpy.display)
+    
+    import renpy.display.render # Most display stuff depends on this. @UnresolvedImport
+    import renpy.display.core # object @UnresolvedImport
+
+    import renpy.text #@UnresolvedImport
+    update_path(renpy.text)
+    
+    import renpy.text.ftfont #@UnresolvedImport
+    import renpy.text.font #@UnresolvedImport
+    import renpy.text.textsupport #@UnresolvedImport
+    import renpy.text.texwrap #@UnresolvedImport
+    import renpy.text.text #@UnresolvedImport
+    import renpy.text.extras #@UnresolvedImport
+    
+    sys.modules['renpy.display.text'] = renpy.text.text
+    
+    import renpy.gl #@UnresolvedImport
+    update_path(renpy.gl)
+    
+    import renpy.angle #@UnresolvedImport
+    update_path(renpy.angle)
+    
+    import renpy.display.layout # core @UnresolvedImport
+    import renpy.display.motion # layout @UnresolvedImport
+    import renpy.display.behavior # layout @UnresolvedImport
+    import renpy.display.transition # core, layout @UnresolvedImport
+    import renpy.display.movetransition # core @UnresolvedImport
+    import renpy.display.im #@UnresolvedImport
+    import renpy.display.imagelike #@UnresolvedImport
+    import renpy.display.image # core, behavior, im, imagelike @UnresolvedImport
+    import renpy.display.video #@UnresolvedImport
+    import renpy.display.focus #@UnresolvedImport
+    import renpy.display.anim #@UnresolvedImport
+    import renpy.display.particle #@UnresolvedImport
+    import renpy.display.joystick #@UnresolvedImport
+    import renpy.display.minigame #@UnresolvedImport
+    import renpy.display.screen #@UnresolvedImport
+    import renpy.display.dragdrop #@UnresolvedImport
+    import renpy.display.imagemap #@UnresolvedImport
+    import renpy.display.predict #@UnresolvedImport
+    
+    import renpy.display.error #@UnresolvedImport
+
+    # Note: For windows to work, renpy.audio.audio needs to be after
+    # renpy.display.module. 
+    import renpy.audio.audio #@UnresolvedImport
+    import renpy.audio.music #@UnresolvedImport
+    import renpy.audio.sound #@UnresolvedImport
+
+    import renpy.ui #@UnresolvedImport
+    import renpy.screenlang #@UnresolvedImport
+
+    import renpy.lint #@UnresolvedImport
+    import renpy.warp #@UnresolvedImport
+
+    import renpy.editor #@UnresolvedImport
+    import renpy.exports #@UnresolvedImport
+    import renpy.character # depends on exports. @UnresolvedImport
+
+    import renpy.dump #@UnresolvedImport
+
+    import renpy.config # depends on lots. @UnresolvedImport
+    import renpy.minstore # depends on lots. @UnresolvedImport
+    import renpy.defaultstore  # depends on everything. @UnresolvedImport
+    import renpy.main #@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']
+    sys.modules['renpy.store'] = sys.modules['store']
+
+    import subprocess
+    sys.modules['renpy.subprocess'] = subprocess
+    
+    for k, v in renpy.defaultstore.__dict__.items():
+        renpy.store.__dict__.setdefault(k, v)
+
+    # Import everything into renpy.exports, provided it isn't
+    # already there.
+    for k, v in globals().items():
+        vars(renpy.exports).setdefault(k, v)
+
+# Fool the analyzer.
+if False:
+    import renpy.defaultstore as store
+
+# This reloads all modules.
+def reload_all():
+    
+    import renpy #@UnresolvedImport
+
+    # Shut down the cache thread.
+    renpy.display.im.cache.quit()
+        
+    blacklist = [ "renpy",
+                  "renpy.log",
+                  "renpy.bootstrap",
+                  "renpy.display",
+                  "renpy.display.pgrender",
+                  "renpy.display.scale" ]
+    
+    for i in list(sys.modules.keys()):
+        if i.startswith("renpy") and i not in blacklist:
+            del sys.modules[i]
+            
+        if i.startswith("store"):
+            del sys.modules[i]
+
+    import gc
+    gc.collect()
+
+    renpy.display.draw = None
+    
+    import_all()
+
+# Information about the platform we're running on. We break the platforms
+# up into 4 groups - windows-like, mac-like, linux-like, and android-like.
+windows = False
+macintosh = False
+linux = False
+android = False
+
+import platform
+
+if platform.win32_ver()[0]:
+    windows = True
+elif platform.mac_ver()[0]:
+    macintosh = True
+else:
+    linux = True
+    
+# The android init code in renpy.py will set linux=False and android=True.
+
+
diff --git a/unrpyc/renpy/ast.py b/unrpyc/renpy/ast.py
new file mode 100644
index 0000000..5bfdb25
--- /dev/null
+++ b/unrpyc/renpy/ast.py
@@ -0,0 +1,1757 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains the AST for the Ren'Py script language. Each class
+# here corresponds to a statement in the script language.
+
+# NOTE:
+# When updating this file, consider if lint.py or warp.py also need
+# updating.
+
+import renpy.display
+
+import re
+import time
+import hashlib
+import collections
+
+def next_node(n):
+    """
+    Indicates the next node that should be executed. When a statement 
+    can crash, this should be set as early as possible, so that ignore
+    can bring us there.
+    """
+
+    renpy.game.context().next_node = n
+
+class ParameterInfo(object):
+    """
+    This class is used to store information about parameters to a
+    label.
+    """
+    def __init__(self, parameters, positional, extrapos, extrakw):
+
+        # A list of parameter name, default value pairs.
+        self.parameters = parameters
+
+        # A list, giving the positional parameters to this function,
+        # in order.
+        self.positional = positional
+
+        # A variable that takes the extra positional arguments, if
+        # any. None if no such variable exists.
+        self.extrapos = extrapos
+
+        # A variable that takes the extra keyword arguments, if
+        # any. None if no such variable exists.
+        self.extrakw = extrakw
+
+class ArgumentInfo(object):
+
+    def __init__(self, arguments, extrapos, extrakw):
+
+        # A list of (keyword, expression) pairs. If an argument doesn't
+        # have a keyword, it's thought of as positional.
+        self.arguments = arguments
+
+        # An expression giving extra positional arguments being
+        # supplied to this function.
+        self.extrapos = extrapos
+
+        # An expression giving extra keyword arguments that need
+        # to be supplied to this function.
+        self.extrakw = extrakw
+
+
+def __newobj__(cls, *args):
+    return cls.__new__(cls, *args)  
+
+# This represents a string containing python code.
+class PyExpr(str):
+
+    __slots__ = [ 
+        'filename',
+        'linenumber',
+        ]
+
+    def __new__(cls, s, filename, linenumber):
+        self = str.__new__(cls, s)
+        self.filename = filename
+        self.linenumber = linenumber
+        return self
+
+    def __getnewargs__(self):
+        return (str(self), self.filename, self.linenumber) # E1101
+    
+class PyCode(object):
+
+    __slots__ = [
+        'source',
+        'location',
+        'mode',
+        'bytecode',
+        'hash',
+        ]
+
+    def __getstate__(self):
+        return (1, self.source, self.location, self.mode)
+
+    def __setstate__(self, state):
+        (_, self.source, self.location, self.mode) = state
+        self.bytecode = None
+
+        if renpy.game.script.record_pycode:
+            renpy.game.script.all_pycode.append(self)
+
+    def __init__(self, source, loc=('<none>', 1), mode='exec'):
+
+        if isinstance(source, PyExpr):
+            loc = (source.filename, source.linenumber, source)
+
+        # The source code.
+        self.source = source
+
+        # The time is necessary so we can disambiguate between Python
+        # blocks on the same line in different script versions.
+        self.location = loc + ( int(time.time()), )
+        self.mode = mode
+
+        # This will be initialized later on, after we are serialized.
+        self.bytecode = None
+
+        if renpy.game.script.record_pycode:
+            renpy.game.script.all_pycode.append(self)
+
+        self.hash = None
+
+    def get_hash(self):
+        try:
+            if self.hash is not None:
+                return self.hash
+        except:
+            pass
+
+        code = self.source
+        if isinstance(code, renpy.python.ast.AST): #@UndefinedVariable
+            code = renpy.python.ast.dump(code) #@UndefinedVariable
+        
+        self.hash = chr(renpy.bytecode_version) + hashlib.md5(repr(self.location) + code.encode("utf-8")).digest()
+        return self.hash
+            
+            
+def chain_block(block, next): #@ReservedAssignment
+    """
+    This is called to chain together all of the nodes in a block. Node
+    n is chained with node n+1, while the last node is chained with
+    next.
+    """
+
+    if not block:
+        return
+
+    for a, b in zip(block, block[1:]):
+        a.chain(b)
+
+    block[-1].chain(next)
+
+
+class Scry(object):
+    """
+    This is used to store information about the future, if we know it. Unlike
+    predict, this tries to only get things we _know_ will happen.
+    """
+
+    # By default, all attributes are None.
+    def __getattr__(self, name):
+        return None
+
+    def __next__(self): #@ReservedAssignment
+        if self._next is None:
+            return None
+        else:
+            return self._next.scry()
+
+    
+class Node(object):
+    """
+    A node in the abstract syntax tree of the program.
+
+    @ivar name: The name of this node.
+
+    @ivar filename: The filename where this node comes from.
+    @ivar linenumber: The line number of the line on which this node is defined.
+    """
+    
+    __slots__ = [
+        'name',
+        'filename',
+        'linenumber',
+        'next',
+        ]
+
+    # True if this node is translatable, false otherwise. (This can be set on
+    # the class or the instance.)
+    translatable = False
+
+    # Called to set the state of a Node, when necessary.
+    def __setstate__(self, state):
+        for k, v in state[1].items():
+            try:           
+                setattr(self, k, v)
+            except AttributeError:
+                pass
+            
+            
+    def __init__(self, loc):
+        """
+        Initializes this Node object.
+
+        @param loc: A (filename, physical line number) tuple giving the
+        logical line on which this Node node starts.
+        """
+
+        self.filename, self.linenumber  = loc
+        self.name = None
+        self.next = None
+        
+    def diff_info(self):
+        """
+        Returns a tuple of diff info about ourself. This is used to
+        compare Nodes to see if they should be considered the same node. The
+        tuple returned must be hashable.
+        """
+
+        return ( id(self), )
+
+    def get_children(self):
+        """
+        Returns a list of all of the nodes that are children of this
+        node. (That is, all of the nodes in any block associated with
+        this node.)
+        """
+
+        return [ ]
+
+    def get_init(self):
+        """
+        Returns a node that should be run at init time (that is, before
+        the normal start of the script.), or None if this node doesn't
+        care to suggest one.
+
+        (The only class that needs to override this is Init.)
+        """
+
+        return None
+        
+    def chain(self, next): #@ReservedAssignment
+        """
+        This is called with the Node node that should be followed after
+        executing this node, and all nodes that this node
+        executes. (For example, if this node is a block label, the
+        next is the node that should be executed after all nodes in
+        the block.)        
+        """
+
+        self.next = next
+
+    def execute(self):
+        """
+        Causes this node to execute, and any action it entails to be
+        performed. The node should call next_node with the node to 
+        be executed after this one.
+        """
+
+        assert False, "Node subclass forgot to define execute."
+
+    def early_execute(self):
+        """
+        Called when the module is loaded.
+        """
+
+    def predict(self):
+        """
+        This is called to predictively load images from this node.  It
+        should cause renpy.display.predict.image and
+        renpy.display.predict.screen to be called as necessary.
+        """
+
+        if self.__next__:
+            return [ self.__next__ ]
+        else:
+            return [ ]
+
+    def scry(self):
+        """
+        Called to return an object with some general, user-definable information
+        about the future.
+        """
+
+        rv = Scry()
+        rv._next = self.__next__ # W0201
+        return rv
+
+    def restructure(self, callback):
+        """
+        Called to restructure the AST.
+
+        When this method is called, callback is called once for each child 
+        block of the node. The block, a list, can be updated by the callback
+        using slice assignment to the list.
+        """
+        
+        # Does nothing for nodes that do not contain child blocks.
+        return
+
+    def get_code(self, dialogue_filter=None):
+        """
+        Returns the canonical form of the code corresponding to this statement.
+        This only needs to be defined if the statement is translatable.
+     
+        `filter`
+            If present, a filter that should be applied to human-readable
+            text in the statement.
+        """
+
+        raise Exception("Not Implemented")
+
+def say_menu_with(expression, callback):
+    """
+    This handles the with clause of a say or menu statement.
+    """
+
+    if expression is not None:
+        what = renpy.python.py_eval(expression)
+    elif renpy.store.default_transition and renpy.game.preferences.transitions == 2:
+        what = renpy.store.default_transition
+    else:
+        return
+
+    if not what:
+        return
+
+    if renpy.game.preferences.transitions:
+        # renpy.game.interface.set_transition(what)
+        callback(what)
+        
+class Say(Node):
+
+    __slots__ = [
+        'who',
+        'who_fast',
+        'what',
+        'with_',
+        'interact',
+        'attributes',
+        ]
+
+    def diff_info(self):
+        return (Say, self.who, self.what)
+
+    def __setstate__(self, state):
+        self.attributes = None
+        self.interact = True
+        Node.__setstate__(self, state)
+
+    def __init__(self, loc, who, what, with_, interact=True, attributes=None):
+
+        super(Say, self).__init__(loc)
+
+        if who is not None:
+            self.who = who.strip()
+
+            if re.match(r'[a-zA-Z_]\w*$', self.who):
+                self.who_fast = True
+            else:
+                self.who_fast = False
+        else:
+            self.who = None 
+            self.who_fast = False
+            
+        self.what = what
+        self.with_ = with_
+        self.interact = interact
+
+        # A tuple of attributes that are applied to the character that's
+        # speaking, or None to disable this behavior.
+        self.attributes = attributes
+
+    def get_code(self, dialogue_filter=None):
+        rv = [ ]
+        
+        if self.who:
+            rv.append(self.who)
+            
+        if self.attributes is not None:
+            rv.extend(self.attributes)
+            
+        what = self.what
+        if dialogue_filter is not None:
+            what = dialogue_filter(what)
+            
+        rv.append(renpy.translation.encode_say_string(what))
+
+        if not self.interact:
+            rv.append("nointeract")
+
+        if self.with_:           
+            rv.append("with")
+            rv.append(self.with_)
+
+        return " ".join(rv)
+
+    def execute(self):
+        
+        next_node(self.__next__)
+
+        try:
+
+            renpy.exports.say_attributes = self.attributes
+            
+            if self.who is not None:
+                if self.who_fast:
+                    who = getattr(renpy.store, self.who, None)
+                    if who is None:
+                        raise Exception("Sayer '%s' is not defined." % self.who.encode("utf-8"))
+                else:
+                    who = renpy.python.py_eval(self.who)
+            else:
+                who = None
+    
+            if not (
+                (who is None) or
+                isinstance(who, collections.Callable) or
+                isinstance(who, str) ):
+    
+                raise Exception("Sayer %s is not a function or string." % self.who.encode("utf-8"))
+                
+            what = self.what
+            if renpy.config.say_menu_text_filter:
+                what = renpy.config.say_menu_text_filter(what) # E1102
+                
+            if getattr(who, "record_say", True):
+                renpy.store._last_say_who = self.who
+                renpy.store._last_say_what = what
+    
+            say_menu_with(self.with_, renpy.game.interface.set_transition)
+            renpy.exports.say(who, what, interact=self.interact)
+
+        finally:
+            renpy.exports.say_attributes = None
+            
+    
+    def predict(self):
+
+        old_attributes = renpy.exports.say_attributes
+
+        try:
+            
+            renpy.exports.say_attributes = self.attributes
+            
+            if self.who is not None:
+                if self.who_fast:
+                    who = getattr(renpy.store, self.who)
+                else:
+                    who = renpy.python.py_eval(self.who)
+            else:
+                who = None
+    
+            def predict_with(trans):
+                renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
+    
+            say_menu_with(self.with_, predict_with)
+    
+            what = self.what
+            if renpy.config.say_menu_text_filter:
+                what = renpy.config.say_menu_text_filter(what)
+    
+            renpy.exports.predict_say(who, what)
+
+        finally:
+            renpy.exports.say_attributes = old_attributes
+        
+        return [ self.__next__ ]
+
+    def scry(self):
+        rv = Node.scry(self)
+
+        if self.who is not None:
+            if self.who_fast:
+                who = getattr(renpy.store, self.who)
+            else:
+                who = renpy.python.py_eval(self.who)
+        else:
+            who = None
+
+        if self.interact:            
+            renpy.exports.scry_say(who, rv)
+        else:
+            rv.interacts = False
+            
+        return rv
+    
+# Copy the descriptor.
+setattr(Say, "with", Say.with_) # E1101
+
+class Init(Node):
+
+    __slots__ = [
+        'block',
+        'priority',
+        ]
+
+    def __init__(self, loc, block, priority):
+        super(Init, self).__init__(loc)
+
+        self.block = block
+        self.priority = priority
+
+
+    def get_children(self):
+        return self.block
+
+    def get_init(self):
+        return self.priority, self.block[0]
+
+    # We handle chaining specially. We want to chain together the nodes in
+    # the block, but we want that chain to end in None, and we also want
+    # this node to just continue on to the next node in normal execution.
+    def chain(self, next): #@ReservedAssignment
+        self.next = next
+
+        chain_block(self.block, None)
+
+    def execute(self):
+        next_node(self.__next__)
+    
+    def restructure(self, callback):
+        callback(self.block)
+    
+def apply_arguments(params, args, kwargs):
+    """
+    Applies arguments to parameters to update scope.
+    
+    `scope`
+        A dict.
+        
+    `params`
+        The parameters object.
+        
+    `args`, `kwargs`
+        Positional and keyword arguments.
+    """
+    
+    values = { }
+    rv = { }
+    
+    if args is None:
+        args = ()
+
+    if kwargs is None:
+        kwargs = { }
+
+    if params is None:
+        if args or kwargs:
+            raise Exception("Arguments supplied, but parameter list not present")
+        else:
+            return rv
+    
+    for name, value in zip(params.positional, args):
+        if name in values:
+            raise Exception("Parameter %s has two values." % name)
+
+        values[name] = value
+
+    extrapos = tuple(args[len(params.positional):])
+
+    for name, value in kwargs.items():
+        if name in values:
+            raise Exception("Parameter %s has two values." % name)
+
+        values[name] = value
+
+    for name, default in params.parameters:
+
+        if name not in values:
+            if default is None:
+                raise Exception("Required parameter %s has no value." % name)
+            else:
+                rv[name] = renpy.python.py_eval(default)
+        
+        else:
+            rv[name] = values[name]
+            del values[name]
+                
+    # Now, values has the left-over keyword arguments, and extrapos
+    # has the left-over positional arguments.
+
+    if params.extrapos:
+        rv[params.extrapos] = extrapos
+    elif extrapos:
+        raise Exception("Too many arguments in call (expected %d, got %d)." % (len(params.positional), len(args)))
+
+    if params.extrakw:
+        rv[params.extrakw] = values
+    else:
+        if values:
+            raise Exception("Unknown keyword arguments: %s" % ( ", ".join(list(values.keys()))))
+
+    return rv
+    
+class Label(Node):
+
+    __slots__ = [
+        'name',
+        'parameters',
+        'block',
+        'hide',
+        ]
+
+    def __setstate__(self, state):
+        self.parameters = None
+        self.hide = False
+        Node.__setstate__(self, state)
+    
+    def __init__(self, loc, name, block, parameters, hide=False):
+        """
+        Constructs a new Label node.
+
+        @param name: The name of this label.
+        @param block: A (potentially empty) list of nodes making up the
+        block associated with this label.
+        """
+
+        super(Label, self).__init__(loc)
+
+        self.name = name
+        self.block = block
+        self.parameters = parameters
+        self.hide = hide
+
+    def diff_info(self):
+        return (Label, self.name)
+
+    def get_children(self):
+        return self.block 
+
+    def chain(self, next): #@ReservedAssignment
+
+        if self.block:
+            self.next = self.block[0]
+            chain_block(self.block, next)
+        else:
+            self.next = next
+            
+    def execute(self):
+        next_node(self.__next__)
+
+        renpy.game.context().mark_seen()
+        
+        values = apply_arguments(self.parameters, renpy.store._args, renpy.store._kwargs)
+
+        for k, v in values.items():
+            renpy.exports.dynamic(k)
+            setattr(renpy.store, k, v)
+
+        renpy.store._args = None
+        renpy.store._kwargs = None
+
+        if renpy.config.label_callback:
+            renpy.config.label_callback(self.name, renpy.game.context().last_abnormal)
+
+    def restructure(self, callback):
+        callback(self.block)
+
+
+class Python(Node):
+
+    __slots__ = [
+        'hide',
+        'code',
+        'store',
+        ]
+
+    def __setstate__(self, state):
+        self.store = "store"
+        super(Python, self).__setstate__(state)
+
+    def __init__(self, loc, python_code, hide=False, store="store"):
+        """
+        @param code: A PyCode object.
+
+        @param hide: If True, the code will be executed with its
+        own local dictionary.
+        """
+        
+        super(Python, self).__init__(loc)
+
+        self.hide = hide
+        self.code = PyCode(python_code, loc=loc, mode='exec')
+        self.store = store
+
+    def diff_info(self):
+        return (Python, self.code.source)
+
+    def early_execute(self):
+        renpy.python.create_store(self.store)
+
+    def execute(self):
+        next_node(self.__next__)
+        
+        try:        
+            renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+        finally:
+            
+            if not renpy.game.context().init_phase:
+                for i in renpy.config.python_callbacks:
+                    i()
+
+    def scry(self):
+        rv = Node.scry(self)
+        rv.interacts = True
+        return rv
+
+class EarlyPython(Node):
+
+    __slots__ = [
+        'hide',
+        'code',
+        'store',
+        ]
+
+    def __setstate__(self, state):
+        self.store = "store"
+        super(EarlyPython, self).__setstate__(state)
+
+    def __init__(self, loc, python_code, hide=False, store="store"):
+        """
+        @param code: A PyCode object.
+
+        @param hide: If True, the code will be executed with its
+        own local dictionary.
+        """
+        
+        super(EarlyPython, self).__init__(loc)
+
+        self.hide = hide
+        self.code = PyCode(python_code, loc=loc, mode='exec')
+        self.store = store
+
+    def diff_info(self):
+        return (EarlyPython, self.code.source)
+
+    def execute(self):
+        next_node(self.__next__)
+
+    def early_execute(self):
+        renpy.python.create_store(self.store)
+        
+        if self.code.bytecode:
+            renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+
+class Image(Node):
+
+    __slots__ = [
+        'imgname',
+        'code',
+        'atl',
+        ]
+
+    def __init__(self, loc, name, expr=None, atl=None):
+        """
+        @param name: The name of the image being defined.
+
+        @param expr: An expression yielding a Displayable that is
+        assigned to the image.
+        """
+
+        super(Image, self).__init__(loc)
+        
+        self.imgname = name
+
+        if expr:
+            self.code = PyCode(expr, loc=loc, mode='eval')
+            self.atl = None
+        else:
+            self.code = None
+            self.atl = atl
+            
+    def diff_info(self): 
+        return (Image, tuple(self.imgname))
+
+    def execute(self):
+
+        # Note: We should always check that self.code is None before
+        # accessing self.atl, as self.atl may not always exist.
+
+        next_node(self.__next__)
+
+        if self.code is not None:
+            img = renpy.python.py_eval_bytecode(self.code.bytecode)
+        else:
+            img = renpy.display.motion.ATLTransform(self.atl)
+
+        renpy.exports.image(self.imgname, img)
+
+
+    
+class Transform(Node):
+
+    __slots__ = [
+
+        # The name of the transform.
+        'varname',
+
+        # The block of ATL associated with the transform.
+        'atl',
+
+        # The parameters associated with the transform, if any.
+        'parameters',        
+        ]
+
+    default_parameters = ParameterInfo([ ], [ ], None, None)
+    
+    def __init__(self, loc, name, atl=None, parameters=default_parameters):
+
+        super(Transform, self).__init__(loc)
+        
+        self.varname = name
+        self.atl = atl
+        self.parameters = parameters
+        
+    def diff_info(self): 
+        return (Transform, self.varname)
+
+    def execute(self):
+
+        next_node(self.__next__)
+
+        parameters = getattr(self, "parameters", None)
+
+        if parameters is None:
+            parameters = Transform.default_parameters
+
+        trans = renpy.display.motion.ATLTransform(self.atl, parameters=parameters)
+        renpy.dump.transforms.append((self.varname, self.filename, self.linenumber))
+        setattr(renpy.store, self.varname, trans)
+
+    
+def predict_imspec(imspec, scene=False, atl=None):
+    """
+    Call this to use the given callback to predict the image named
+    in imspec.
+    """
+
+    if len(imspec) == 7:
+        name, expression, tag, _at_list, layer, _zorder, _behind = imspec
+
+    elif len(imspec) == 6:
+        name, expression, tag, _at_list, layer, _zorder = imspec
+
+    elif len(imspec) == 3:
+        name, _at_list, layer = imspec
+        
+        
+    if expression:
+        try:
+            img = renpy.python.py_eval(expression)
+            img = renpy.easy.displayable(img)
+        except:
+            return
+
+    else:
+        img = renpy.display.image.images.get(name, None)
+        if img is None:
+            return
+
+    full_name = name
+    if tag:
+        full_name = (tag,) + full_name[1:]
+
+    if scene:
+        renpy.game.context().images.predict_scene(layer)
+        
+    renpy.game.context().images.predict_show(tag or name, layer)
+    
+    if atl is not None:
+        try:
+            img = renpy.display.motion.ATLTransform(atl, child=img)
+        except:
+            import traceback
+            traceback.print_exc()
+        
+    renpy.display.predict.displayable(img)
+
+
+    
+def show_imspec(imspec, atl=None):
+
+    if len(imspec) == 7:
+        name, expression, tag, at_list, layer, zorder, behind = imspec
+        
+    elif len(imspec) == 6:
+        name, expression, tag, at_list, layer, zorder = imspec
+        behind = [ ]
+        
+    elif len(imspec) == 3:
+        name, at_list, layer = imspec
+        expression = None
+        tag = None
+        zorder = None
+        behind = [ ]
+        
+    if zorder is not None:
+        zorder = renpy.python.py_eval(zorder)
+    else:
+        zorder = 0
+
+    if expression is not None:
+        expression = renpy.python.py_eval(expression)
+        expression = renpy.easy.displayable(expression)
+
+    at_list = [ renpy.python.py_eval(i) for i in at_list ]
+
+    renpy.config.show(name,
+                      at_list=at_list,
+                      layer=layer,
+                      what=expression,
+                      zorder=zorder,
+                      tag=tag,
+                      behind=behind,
+                      atl=atl)
+
+class Show(Node):
+
+    __slots__ = [
+        'imspec',
+        'atl',
+        ]
+
+    def __init__(self, loc, imspec, atl=None):
+        """
+        @param imspec: A triple consisting of an image name (itself a
+        tuple of strings), a list of at expressions, and a layer.
+        """
+
+        super(Show, self).__init__(loc)
+
+        self.imspec = imspec
+        self.atl = atl
+        
+    def diff_info(self): 
+        return (Show, tuple(self.imspec[0]))
+
+    def execute(self):
+        next_node(self.__next__)
+        
+        show_imspec(self.imspec, atl=getattr(self, "atl", None))
+
+    def predict(self):
+        predict_imspec(self.imspec, atl=getattr(self, "atl", None))
+        return [ self.__next__ ]
+        
+
+class Scene(Node):
+
+    __slots__ = [
+        'imspec',
+        'layer',
+        'atl',
+        ]
+
+    def __init__(self, loc, imgspec, layer, atl=None):
+        """
+        @param imspec: A triple consisting of an image name (itself a
+        tuple of strings), a list of at expressions, and a layer, or
+        None to not have this scene statement also display an image.
+        """
+
+        super(Scene, self).__init__(loc)
+
+        self.imspec = imgspec
+        self.layer = layer
+        self.atl = atl
+
+    def diff_info(self): 
+
+        if self.imspec:
+            data = tuple(self.imspec[0])
+        else:
+            data = None
+
+        return (Scene, data)
+
+    def execute(self):
+
+        next_node(self.__next__)
+
+        renpy.config.scene(self.layer)
+
+        if self.imspec:
+            show_imspec(self.imspec, atl=getattr(self, "atl", None))
+        
+    def predict(self):
+        
+        if self.imspec:
+            predict_imspec(self.imspec, atl=getattr(self, "atl", None), scene=True)
+
+        return [ self.__next__ ]
+
+    
+class Hide(Node):
+
+    __slots__ = [
+        'imspec',
+        ]
+
+    def __init__(self, loc, imgspec):
+        """
+        @param imspec: A triple consisting of an image name (itself a
+        tuple of strings), a list of at expressions, and a list of
+        with expressions.
+        """
+
+        super(Hide, self).__init__(loc)
+
+        self.imspec = imgspec
+
+    def diff_info(self): 
+        return (Hide, tuple(self.imspec[0]))
+
+    def predict(self):
+
+        if len(self.imspec) == 3:
+            name, _at_list, layer = self.imspec
+            tag = None
+            _expression = None
+            _zorder = None
+            _behind = None
+        elif len(self.imspec) == 6:
+            name, _expression, tag, _at_list, layer, _zorder = self.imspec
+            _behind = None
+        elif len(self.imspec) == 7:
+            name, _expression, tag, _at_list, layer, _zorder, _behind = self.imspec
+
+
+        if tag is None:
+            tag = name[0]
+            
+        renpy.game.context().images.predict_hide(tag, layer)
+
+        return [ self.__next__ ]
+        
+    def execute(self):
+
+        next_node(self.__next__)
+
+        if len(self.imspec) == 3:
+            name, _at_list, layer = self.imspec
+            _expression = None
+            tag = None
+            _zorder = 0
+        elif len(self.imspec) == 6:
+            name, _expression, tag, _at_list, layer, _zorder = self.imspec
+        elif len(self.imspec) == 7:
+            name, _expression, tag, _at_list, layer, _zorder, _behind = self.imspec
+            
+        renpy.config.hide(tag or name, layer)
+
+    
+class With(Node):
+
+    __slots__ = [
+        'expr',
+        'paired',
+        ]
+
+    def __setstate__(self, state):
+        self.paired = None
+        Node.__setstate__(self, state)
+    
+    def __init__(self, loc, expr, paired=None):
+        """
+        @param expr: An expression giving a transition or None.
+        """
+
+        super(With, self).__init__(loc)
+        self.expr = expr
+        self.paired = paired
+
+    def diff_info(self):
+        return (With, self.expr)
+        
+    def execute(self):
+
+        next_node(self.__next__)
+
+        trans = renpy.python.py_eval(self.expr)
+
+        if self.paired is not None:
+            paired = renpy.python.py_eval(self.paired)
+        else:
+            paired = None 
+
+        renpy.exports.with_statement(trans, paired)
+
+    def predict(self):
+
+        try:
+            trans = renpy.python.py_eval(self.expr)
+
+            if trans:
+                renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
+
+        except:
+            pass
+
+                
+        return [ self.__next__ ]
+    
+        
+class Call(Node):
+
+    __slots__ = [
+        'label',
+        'arguments',
+        'expression',
+        ]
+
+    def __setstate__(self, state):
+        self.arguments = None
+        Node.__setstate__(self, state)
+    
+    def __init__(self, loc, label, expression, arguments):
+
+        super(Call, self).__init__(loc)
+        self.label = label
+        self.expression = expression
+        self.arguments = arguments
+
+    def diff_info(self):
+        return (Call, self.label, self.expression)
+
+    def execute(self):
+
+        label = self.label
+        if self.expression:
+            label = renpy.python.py_eval(label)
+
+        rv = renpy.game.context().call(label, return_site=self.next.name)
+        next_node(rv)
+        renpy.game.context().abnormal = True
+
+        if self.arguments:
+
+            args = [ ]
+            kwargs = renpy.python.RevertableDict()
+
+            for name, expr in self.arguments.arguments:
+
+                value = renpy.python.py_eval(expr)
+
+                if name is None:
+                    args.append(value)
+                else:
+                    if name in kwargs:
+                        raise Exception("The argument named %s appears twice." % name)
+
+                    kwargs[name] = value
+
+            if self.arguments.extrapos:
+                args.extend(renpy.python.py_eval(self.arguments.extrapos))
+
+            if self.arguments.extrakw:
+                for name, value in renpy.python.py_eval(self.arguments.extrakw).items():
+                    if name in kwargs:
+                        raise Exception("The argument named %s appears twice." % name)
+
+                    kwargs[name] = value
+
+
+            renpy.store._args = tuple(args)
+            renpy.store._kwargs = kwargs
+                            
+    def predict(self):
+        if self.expression:
+            return [ ]
+        else:
+            return [ renpy.game.script.lookup(self.label) ]
+
+    def scry(self):
+        rv = Node.scry(self)
+        rv._next = None
+        return rv
+
+
+class Return(Node):
+
+    __slots__ = [ 'expression']
+
+    def __setstate__(self, state):
+        self.expression = None
+        Node.__setstate__(self, state)
+    
+    def __init__(self, loc, expression):
+        super(Return, self).__init__(loc)
+        self.expression = expression
+        
+    def diff_info(self):
+        return (Return, )
+
+    # We don't care what the next node is.
+    def chain(self, next): #@ReservedAssignment
+        self.next = None
+        return
+
+    def execute(self):
+
+        if self.expression:
+            renpy.store._return = renpy.python.py_eval(self.expression)
+        else:
+            renpy.store._return = None
+
+        renpy.game.context().pop_dynamic()
+            
+        next_node(renpy.game.context().lookup_return(pop=True))
+
+    def predict(self):
+        site = renpy.game.context().lookup_return(pop=False)
+        if site:
+            return [ site ]
+        else:
+            return [ ]
+
+    def scry(self):
+        rv = Node.scry(self)
+        rv._next = None
+        return rv
+
+    
+class Menu(Node):
+
+    __slots__ = [
+        'items',
+        'set',
+        'with_',
+        ]
+
+    def __init__(self, loc, items, set, with_): #@ReservedAssignment
+        super(Menu, self).__init__(loc)
+
+        self.items = items
+        self.set = set
+        self.with_ = with_
+
+    def diff_info(self):
+        return (Menu,)
+
+    def get_children(self):
+        rv = [ ]
+
+        for _label, _condition, block in self.items:
+            if block:
+                rv.extend(block)
+
+        return rv
+
+    # Blocks of statements in a choice continue after the menu.
+    def chain(self, next): #@ReservedAssignment
+
+        self.next = next
+
+        for (_label, _condition, block) in self.items:
+            if block:
+                chain_block(block, next)
+
+    def execute(self):
+
+        next_node(self.__next__)
+
+        choices = [ ]
+        narration = [ ]        
+        
+        for i, (label, condition, block) in enumerate(self.items):
+
+            if renpy.config.say_menu_text_filter:
+                label = renpy.config.say_menu_text_filter(label)
+
+            if block is None:
+                if renpy.config.narrator_menu and label:
+                    narration.append(label)
+                else:
+                    choices.append((label, condition, None))
+            else:
+                choices.append((label, condition, i))
+                next_node(block[0])
+
+        if narration:
+            renpy.exports.say(None, "\n".join(narration), interact=False)
+                
+        say_menu_with(self.with_, renpy.game.interface.set_transition)
+        choice = renpy.exports.menu(choices, self.set)
+
+        if choice is not None:
+            next_node(self.items[choice][2][0])
+        else:
+            next_node(self.__next__)
+        
+
+    def predict(self):
+        rv = [ ]
+
+        def predict_with(trans):
+            renpy.display.predict.displayable(trans(old_widget=None, new_widget=None))
+
+        say_menu_with(self.with_, predict_with)
+
+        renpy.store.predict_menu()
+        
+        for _label, _condition, block in self.items:
+            if block:
+                rv.append(block[0])
+
+        return rv
+
+    def scry(self):
+        rv = Node.scry(self)
+        rv._next = None
+        rv.interacts = True
+        return rv
+    
+    def restructure(self, callback):
+        for _label, _condition, block in self.items:
+            if block is not None:
+                callback(block)
+    
+setattr(Menu, "with", Menu.with_) # E1101
+
+
+# Goto is considered harmful. So we decided to name it "jump"
+# instead. 
+class Jump(Node):
+
+    __slots__ = [
+        'target',
+        'expression',
+        ]
+
+    def  __init__(self, loc, target, expression):
+        super(Jump, self).__init__(loc)
+
+        self.target = target
+        self.expression = expression
+
+    def diff_info(self):
+        return (Jump, self.target, self.expression)
+
+    # We don't care what our next node is.
+    def chain(self, next): #@ReservedAssignment
+        self.next = None
+        return
+
+    def execute(self):
+
+        target = self.target
+        if self.expression:
+            target = renpy.python.py_eval(target)
+
+        rv = renpy.game.script.lookup(target)
+        renpy.game.context().abnormal = True
+
+        next_node(rv)
+
+    def predict(self):
+
+        if self.expression:
+            return [ ]
+        else:
+            return [ renpy.game.script.lookup(self.target) ]
+
+    def scry(self):
+        rv = Node.scry(self)
+        if self.expression:
+            rv._next = None
+        else:
+            rv._next = renpy.game.script.lookup(self.target)
+            
+        return rv
+
+
+# GNDN
+class Pass(Node):
+
+    __slots__ = [ ]
+
+    def diff_info(self):
+        return (Pass,)
+
+    def execute(self):
+        next_node(self.__next__)
+
+
+class While(Node):
+
+    __slots__ = [
+        'condition',
+        'block',
+        ]
+
+    def __init__(self, loc, condition, block):
+        super(While, self).__init__(loc)
+
+        self.condition = condition
+        self.block = block
+
+    def diff_info(self):
+        return (While, self.condition)
+
+    def get_children(self):
+        return self.block
+
+    def chain(self, next): #@ReservedAssignment
+        self.next = next
+        chain_block(self.block, self)
+
+    def execute(self):
+
+        next_node(self.__next__)
+
+        if renpy.python.py_eval(self.condition):
+            next_node(self.block[0])
+
+    def predict(self):
+        return [ self.block[0], self.__next__ ]
+        
+    def scry(self):
+        rv = Node.scry(self)
+        rv._next = None
+        return rv
+
+    def restructure(self, callback):
+        callback(self.block)
+
+class If(Node):
+
+    __slots__ = [ 'entries' ]
+
+    def __init__(self, loc, entries):
+        """
+        @param entries: A list of (condition, block) tuples.
+        """
+
+        super(If, self).__init__(loc)
+
+        self.entries = entries
+
+    def diff_info(self):
+        return (If,)
+
+    def get_children(self):
+        rv = [ ]
+
+        for _condition, block in self.entries:
+            rv.extend(block)
+
+        return rv
+
+    def chain(self, next): #@ReservedAssignment
+        self.next = next
+
+        for _condition, block in self.entries:
+            chain_block(block, next)
+
+    def execute(self):
+
+        next_node(self.__next__)
+
+        for condition, block in self.entries:
+            if renpy.python.py_eval(condition):
+                next_node(block[0])
+                return
+
+    def predict(self):
+
+        return [ block[0] for _condition, block in self.entries ] + \
+               [ self.__next__ ]
+
+    def scry(self):
+        rv = Node.scry(self)
+        rv._next = None
+        return rv
+
+    def restructure(self, callback):
+        for _condition, block in self.entries:
+            callback(block)
+
+class UserStatement(Node):
+
+    __slots__ = [ 
+        'line', 
+        'parsed', 
+        'block', 
+        'translatable' ]
+
+    def __setstate__(self, state):
+        self.block = [ ]
+        self.translatable = False
+        Node.__setstate__(self, state)
+     
+    def __init__(self, loc, line, block):
+
+        super(UserStatement, self).__init__(loc)
+        self.line = line
+        self.block = block
+        self.parsed = None
+
+        # Do not store the parse quite yet.
+        _parse_info = renpy.statements.parse(self, self.line, self.block)
+        
+    def diff_info(self):
+        return (UserStatement, self.line)
+
+    def execute(self):
+        next_node(self.get_next())
+        
+        self.call("execute")
+
+    def predict(self):
+        self.call("predict")            
+        return [ self.get_next() ]
+    
+    def call(self, method, *args, **kwargs):
+        
+        parsed = self.parsed        
+        if parsed is None:
+            parsed = renpy.statements.parse(self, self.line, self.block)
+            self.parsed = parsed
+
+        renpy.statements.call(method, parsed, *args, **kwargs)
+
+    def get_next(self):
+        rv = self.call("next")
+        if rv is not None:
+            return renpy.game.script.lookup(rv)
+        else:
+            return self.__next__
+        
+    def scry(self):
+        rv = Node.scry(self)
+        rv._next = self.get_next()
+        self.call("scry", rv)
+        return rv
+     
+    def get_code(self, dialogue_filter=None):
+        return self.line
+                            
+            
+class Define(Node):
+
+    __slots__ = [
+        'varname',
+        'code',
+        ]
+
+    def __init__(self, loc, name, expr):
+        """
+        @param name: The name of the image being defined.
+
+        @param expr: An expression yielding a Displayable that is
+        assigned to the image.
+        """
+
+        super(Define, self).__init__(loc)
+        
+        self.varname = name
+        self.code = PyCode(expr, loc=loc, mode='eval')
+            
+    def diff_info(self): 
+        return (Define, tuple(self.varname))
+
+    def execute(self):
+
+        next_node(self.__next__)
+
+        value = renpy.python.py_eval_bytecode(self.code.bytecode)
+        renpy.dump.definitions.append((self.varname, self.filename, self.linenumber))
+        setattr(renpy.store, self.varname, value)    
+
+
+class Screen(Node):
+
+    __slots__ = [
+        'screen',
+        ]
+
+    def __init__(self, loc, screen):
+        """
+        @param name: The name of the image being defined.
+
+        @param expr: An expression yielding a Displayable that is
+        assigned to the image.
+        """
+
+        super(Screen, self).__init__(loc)
+        
+        self.screen = screen
+            
+    def diff_info(self): 
+        return (Screen, self.screen.name)
+
+    def execute(self):
+        next_node(self.__next__)
+        self.screen.define()
+        renpy.dump.screens.append((self.screen.name, self.filename, self.linenumber))
+
+
+################################################################################
+# Translations
+################################################################################
+
+class Translate(Node):
+    """
+    A translation block, produced either by explicit translation statements 
+    or implicit translation blocs.
+    
+    If language is None, when executed this transfers control to the translate 
+    statement in the current language, if any, and otherwise runs the block.
+    If language is not None, causes an error to occur if control reaches this 
+    statement.
+
+    When control normally leaves a translate statement, in any language, it 
+    goes to the end of the translate statement in the None language.
+    """
+    
+    __slots__ = [
+        "identifier",
+        "language",
+        "block",
+        ]
+    
+    def __init__(self, loc, identifier, language, block):
+        super(Translate, self).__init__(loc)
+        
+        self.identifier = identifier
+        self.language = language
+        self.block = block
+        
+    def diff_info(self):
+        return (Translate, self.identifier, self.language)
+
+    def chain(self, next): #@ReservedAssignment
+        self.next = next
+        chain_block(self.block, next)
+
+    def execute(self):
+
+        if self.language is not None:
+            next_node(self.__next__)
+            raise Exception("Translation nodes cannot be run directly.")
+        
+        next_node(renpy.game.script.translator.lookup_translate(self.identifier))
+        renpy.game.context().translate_identifier = self.identifier
+
+    def predict(self):
+        node = renpy.game.script.translator.lookup_translate(self.identifier)
+        return [ node ]
+    
+    def scry(self):
+        rv = Scry()
+        rv._next = renpy.game.script.translator.lookup_translate(self.identifier)
+        return rv
+    
+    def get_children(self):
+        return self.block
+    
+    def restructure(self, callback):
+        return callback(self.block)
+    
+    
+class EndTranslate(Node):
+    """
+    A node added implicitly after each translate block. It's responsible for
+    resetting the translation identifier.
+    """
+    
+    def __init__(self, loc):
+        super(EndTranslate, self).__init__(loc)
+        
+    def diff_info(self):
+        return (EndTranslate,)
+    
+    def execute(self):
+        next_node(self.__next__)
+        renpy.game.context().translate_identifier = None
+        
+
+class TranslateString(Node):
+    """
+    A node used for translated strings.
+    """
+    
+    __slots__ = [ 
+        "language",
+        "old",
+        "new"
+        ]
+    
+    def __init__(self, loc, language, old, new):
+        super(TranslateString, self).__init__(loc)
+        self.language = language
+        self.old = old
+        self.new = new
+        
+    def diff_info(self):
+        return (TranslateString,)
+    
+    def execute(self):
+        next_node(self.__next__)
+        renpy.translation.add_string_translation(self.language, self.old, self.new)
+
+class TranslatePython(Node):
+
+    __slots__ = [
+        'language',
+        'code',
+        ]
+
+    def __init__(self, loc, language, python_code):
+        """
+        @param code: A PyCode object.
+
+        @param hide: If True, the code will be executed with its
+        own local dictionary.
+        """
+        
+        super(TranslatePython, self).__init__(loc)
+
+        self.language = language
+        self.code = PyCode(python_code, loc=loc, mode='exec')
+
+    def diff_info(self):
+        return (TranslatePython, self.code.source)
+
+    def execute(self):
+        next_node(self.__next__)
+
+    # def early_execute(self):
+    #    renpy.python.create_store(self.store)
+    #    renpy.python.py_exec_bytecode(self.code.bytecode, self.hide, store=self.store)
+
+            
diff --git a/unrpyc/renpy/atl.py b/unrpyc/renpy/atl.py
new file mode 100644
index 0000000..f314e3e
--- /dev/null
+++ b/unrpyc/renpy/atl.py
@@ -0,0 +1,1544 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy.display
+import random
+
+def compiling(loc):
+    file, number = loc #@ReservedAssignment
+
+    renpy.game.exception_info = "Compiling ATL code at %s:%d" % (file, number)
+
+def executing(loc):
+    file, number = loc #@ReservedAssignment
+    
+    renpy.game.exception_info = "Executing ATL code at %s:%d" % (file, number)
+
+
+# A map from the name of a time warp function to the function itself.
+warpers = { }
+
+def atl_warper(f):
+    name = f.__name__
+    warpers[name] = f
+    return f
+    
+# The pause warper is used internally when no other warper is
+# specified.
+@atl_warper
+def pause(t):
+    if t < 1.0:
+        return 0.0
+    else:
+        return 1.0
+
+position = object()
+    
+# A dictionary giving property names and the corresponding default
+# values.
+PROPERTIES = {
+        "pos" : (position, position),
+        "xpos" : position,
+        "ypos" : position,
+        "anchor" : (position, position),
+        "xanchor" : position,
+        "yanchor" : position,
+        "xaround" : position,
+        "yaround" : position,
+        "xanchoraround" : float,
+        "yanchoraround" : float,
+        "align" : (float, float),
+        "xalign" : float,
+        "yalign" : float,
+        "rotate" : float,
+        "rotate_pad" : bool,
+        "transform_anchor" : bool,
+        "xzoom" : float,
+        "yzoom" : float,
+        "zoom" : float,
+        "alpha" : float,
+        "around" : (position, position),
+        "alignaround" : (float, float),
+        "angle" : float,
+        "radius" : float,
+        "crop" : (float, float, float, float),
+        "size" : (int, int),
+        "corner1" : (float, float),
+        "corner2" : (float, float),
+        "subpixel" : bool,
+        "delay" : float,
+        "xoffset" : float,
+        "yoffset" : float,
+        "offset" : (int, int),
+        "xcenter" : position,
+        "ycenter" : position,
+        }
+
+
+def correct_type(v, b, ty):
+    """
+    Corrects the type of v to match ty. b is used to inform the match.
+    """
+
+    if ty is position:
+        if v is None:
+            return None
+        else:
+            return type(b)(v)
+    else:
+        return ty(v)
+
+
+def interpolate(t, a, b, type): #@ReservedAssignment
+    """
+    Linearly interpolate the arguments. 
+    """
+
+    if t >= 1.0:
+        return b
+    
+    # Recurse into tuples.
+    if isinstance(b, tuple):        
+        if a is None:
+            a = [ None ] * len(b)
+        
+        return tuple(interpolate(t, i, j, ty) for i, j, ty in zip(a, b, type))
+
+    # Deal with booleans, nones, etc.
+    elif b is None or isinstance(b, bool):
+        if t >= 1.0:
+            return b
+        else:
+            return a
+
+    # Interpolate everything else.
+    else:
+        if a is None:
+            a = 0
+            
+        return correct_type(a + t * (b - a), b, type)
+
+# Interpolate the value of a spline. This code is based on Aenakume's code,
+# from 00splines.rpy.
+def interpolate_spline(t, spline):
+
+    if isinstance(spline[-1], tuple):
+        return tuple(interpolate_spline(t, i) for i in zip(*spline))
+
+    if spline[0] is None:
+        return spline[-1]
+        
+    if len(spline) == 2:
+        t_p = 1.0 - t        
+
+        rv = t_p * spline[0] + t * spline[-1]
+
+    elif len(spline) == 3:
+        t_pp = (1.0 - t)**2
+        t_p = 2 * t * (1.0 - t)
+        t2 = t**2
+        
+        rv = t_pp * spline[0] + t_p * spline[1] + t2 * spline[2]
+
+    elif len(spline) == 4:
+
+        t_ppp = (1.0 - t)**3
+        t_pp = 3 * t * (1.0 - t)**2
+        t_p = 3 * t**2 * (1.0 - t)
+        t3 = t**3
+        
+        rv = t_ppp * spline[0] + t_pp * spline[1] + t_p * spline[2] + t3 * spline[3]
+
+    else:
+        raise Exception("ATL can't interpolate splines of length %d." % len(spline))
+
+    return correct_type(rv, spline[-1], position)
+    
+
+# This is the context used when compiling an ATL statement. It stores the
+# scopes that are used to evaluate the various expressions in the statement,
+# and has a method to do the evaluation and return a result.
+class Context(object):
+    def __init__(self, context):
+        self.context = context
+
+    def eval(self, expr): #@ReservedAssignment
+        expr = renpy.python.escape_unicode(expr)
+        return eval(expr, renpy.store.__dict__, self.context) #@UndefinedVariable
+    
+# This is intended to be subclassed by ATLTransform. It takes care of
+# managing ATL execution, which allows ATLTransform itself to not care
+# much about the contents of this file.
+class ATLTransformBase(renpy.object.Object):
+
+    # Compatibility with older saves.
+    parameters = renpy.ast.ParameterInfo([ ], [ ], None, None) 
+    
+    def __init__(self, atl, context, parameters):
+
+        # The constructor will be called by atltransform.
+
+        if parameters is None:
+            parameters = ATLTransformBase.parameters
+
+        # The parameters that we take.
+        self.parameters = parameters
+        
+        # The raw code that makes up this ATL statement.
+        self.atl = atl
+
+        # The context in which execution occurs.
+        self.context = Context(context)
+        
+        # The code after it has been compiled into a block.
+        self.block = None
+
+        # The properties of the block, if it contains only an
+        # Interpolation.
+        self.properties = None
+
+        # The state of the statement we are executing. As this can be
+        # shared between more than one object (in the case of a hide),
+        # the data must not be altered.
+        self.atl_state = None
+
+        # Are we done?
+        self.done = False
+
+        # The transform event we are going to process.
+        self.transform_event = None
+
+        # The transform event we last processed.
+        self.last_transform_event = None
+
+        # The child transform event we last processed.
+        self.last_child_transform_event = None
+
+    def take_execution_state(self, t):
+        """
+        Updates self to begin executing from the same point as t. This
+        requires that t.atl is self.atl.
+        """
+
+        super(ATLTransformBase, self).take_execution_state(t)
+        
+        if t.atl is not self.atl:
+            return
+
+        self.done = t.done
+        self.block = t.block
+        self.atl_state = t.atl_state
+        self.transform_event = t.transform_event
+        self.last_transform_event = t.last_transform_event
+        self.last_child_transform_event = t.last_child_transform_event
+
+        self.st = t.st
+        self.at = t.at
+        self.st_offset = t.st_offset
+        self.at_offset = t.at_offset
+
+        if self.child is renpy.display.motion.null:
+            self.child = t.child
+        
+
+    def __call__(self, *args, **kwargs):
+
+        context = self.context.context.copy()
+
+        for k, v in self.parameters.parameters:
+            if v is not None:
+                context[k] = renpy.python.py_eval(v)
+
+        positional = list(self.parameters.positional)
+        args = list(args)
+
+        child = None
+
+        if not positional and args:
+            child = args.pop(0)
+        
+        # Handle positional arguments.
+        while positional and args:
+            name = positional.pop(0)
+            value = args.pop(0)
+
+            if name in kwargs:
+                raise Exception('Parameter %r is used as both a positional and keyword argument to a transition.' % name)
+
+            context[name] = value
+
+        if args:
+            raise Exception("Too many arguments passed to ATL transform.")
+
+        # Handle keyword arguments.
+        for k, v in kwargs.items():
+
+            if k in positional:
+                positional.remove(k)
+                context[k] = v
+            elif k in context:
+                context[k] = v
+            elif k == 'child':
+                child = v
+            else:
+                raise Exception('Parameter %r is not known by ATL Transform.' % k)
+
+        if child is None:
+            child = self.child
+            
+        if child is None:
+            child = renpy.display.motion.get_null()
+
+        # Create a new ATL Transform.
+        parameters = renpy.ast.ParameterInfo({}, positional, None, None)
+
+        rv = renpy.display.motion.ATLTransform(
+            atl=self.atl,
+            child=child,
+            style=self.style_arg,
+            context=context,
+            parameters=parameters)
+
+        rv.take_state(self)
+        
+        return rv
+
+    
+    def compile(self): #@ReservedAssignment
+        """
+        Compiles the ATL code into a block. As necessary, updates the
+        properties.
+        """
+
+        if self.parameters.positional and self.parameters.positional[0][1] is None:
+            raise Exception("Cannot compile ATL Transform, as it's missing positional parameter %s." % self.parameters.positional[0])
+        
+        old_exception_info = renpy.game.exception_info
+        
+        self.block = self.atl.compile(self.context)
+
+        if len(self.block.statements) == 1 \
+                and isinstance(self.block.statements[0], Interpolation):
+
+            interp = self.block.statements[0]
+
+            if interp.duration == 0 and interp.properties:
+                self.properties = interp.properties[:]
+
+        renpy.game.exception_info = old_exception_info
+
+        
+    def execute(self, trans, st, at):
+
+        if self.done:
+            return None
+
+        if not self.block:
+            self.compile()
+
+        # Propagate transform_events from children.
+        if self.child:
+            if self.child.transform_event != self.last_child_transform_event:
+                self.last_child_transform_event = self.child.transform_event
+                self.transform_event = self.child.transform_event
+                
+        # Hide request.
+        if trans.hide_request:
+            self.transform_event = "hide"
+
+        if trans.replaced_request:
+            self.transform_event = "replaced"
+            
+        # Notice transform events.
+        if self.transform_event != self.last_transform_event:
+            event = self.transform_event
+            self.last_transform_event = self.transform_event
+        else:
+            event = None
+            
+        old_exception_info = renpy.game.exception_info
+
+        if self.atl.animation:
+            timebase = at
+        else:
+            timebase = st
+
+        action, arg, pause = self.block.execute(trans, timebase, self.atl_state, event)
+
+        renpy.game.exception_info = old_exception_info
+
+        # print "Executing", self, self.state, self.xpos, self.ypos
+        
+        if action == "continue":
+            self.atl_state = arg
+        else:
+            self.done = True
+
+        return pause
+    
+    def predict_one(self):
+        self.atl.predict(self.context)
+        
+    def visit(self):
+        if not self.block:
+            self.compile()
+
+        return self.children + self.block.visit()
+        
+    
+# The base class for raw ATL statements.
+class RawStatement(renpy.object.Object):
+
+    def __init__(self, loc):
+        super(RawStatement, self).__init__()
+        self.loc = loc
+        
+    # Compiles this RawStatement into a Statement, by using ctx to
+    # evaluate expressions as necessary.
+    def compile(self, ctx): #@ReservedAssignment
+        raise Exception("Compile not implemented.")
+
+    # Predicts the images used by this statement.
+    def predict(self, ctx):
+        return
+
+# The base class for compiled ATL Statements.
+class Statement(renpy.object.Object):
+
+    def __init__(self, loc):
+        super(Statement, self).__init__()
+        self.loc = loc
+
+    # trans is the transform we're working on.
+    # st is the time since this statement started executing.
+    # state is the state stored by this statement, or None if
+    # we've just started executing this statement.
+    # event is an event we're triggering.
+    #
+    # "continue", state, pause - Causes this statement to execute
+    # again, with the given state passed in the second time around.
+    #
+    # 
+    # "next", timeleft, pause - Causes the next statement to execute,
+    # with timeleft being the amount of time left after this statement
+    # finished.
+    #
+    # "event", (name, timeleft), pause - Causes an event to be reported,
+    # and control to head up to the event handler.
+    #
+    # "repeat", (count, timeleft), pause - Causes the repeat behavior
+    # to occur.
+    #
+    # As the Repeat statement can only appear in a block, only Block
+    # needs to deal with the repeat behavior.
+    #
+    # Pause is the amount of time until execute should be called again,
+    # or None if there's no need to call execute ever again.
+    def execute(self, trans, st, state, event):
+        raise Exception("Not implemented.")
+
+    # Return a list of displayable children.
+    def visit(self):
+        return [ ]
+
+# This represents a Raw ATL block.
+class RawBlock(RawStatement):
+
+    # Should we use the animation timebase or the showing timebase?
+    animation = False
+
+    def __init__(self, loc, statements, animation):
+
+        super(RawBlock, self).__init__(loc)
+        
+        # A list of RawStatements in this block.
+        self.statements = statements
+        
+        self.animation = animation
+        
+    def compile(self, ctx): #@ReservedAssignment
+        compiling(self.loc)
+
+        statements = [ i.compile(ctx) for i in self.statements ]
+
+        return Block(self.loc, statements)
+
+    def predict(self, ctx):
+        for i in self.statements:
+            i.predict(ctx)
+    
+    
+# A compiled ATL block. 
+class Block(Statement):
+    def __init__(self, loc, statements):
+
+        super(Block, self).__init__(loc)
+        
+        # A list of statements in the block.
+        self.statements = statements
+
+        # The start times of various statements.
+        self.times = [ ]
+        
+        for i, s in enumerate(statements):
+            if isinstance(s, Time):
+                self.times.append((s.time, i + 1))
+
+        self.times.sort()
+        
+    def execute(self, trans, st, state, event):
+
+        executing(self.loc)
+        
+        # Unpack the state.
+        if state is not None:
+            index, start, loop_start, repeats, times, child_state = state
+        else:
+            index, start, loop_start, repeats, times, child_state = 0, 0, 0, 0, self.times[:], None
+
+        # What we might be returning.
+        action = "continue"
+        arg = None
+        pause = None
+        
+        while action == "continue":
+
+            # Target is the time we're willing to execute to.
+            # Max_pause is how long we'll wait before executing again.
+
+            # If we have times queued up, then use them to inform target
+            # and time.
+            if times:
+                time, tindex = times[0]
+                target = min(time, st)
+                max_pause = time - target
+
+            # Otherwise, take the defaults.
+            else:
+                target = st
+                max_pause = 15
+
+            while True:
+
+                # If we've hit the last statement, it's the end of
+                # this block.
+                if index >= len(self.statements):
+                    return "next", target - start, None
+
+
+                # Find the statement and try to run it.
+                stmt = self.statements[index]
+                action, arg, pause = stmt.execute(trans, target - start, child_state, event)
+
+                # On continue, persist our state.
+                if action == "continue":
+                    if pause is None:
+                        pause = max_pause
+
+                    action, arg, pause = "continue", (index, start, loop_start, repeats, times, arg), min(max_pause, pause)
+                    break
+
+                elif action == "event":
+                    return action, arg, pause
+
+                # On next, advance to the next statement in the block.
+                elif action == "next":
+                    index += 1
+                    start = target - arg
+                    child_state = None
+
+                # On repeat, either terminate the block, or go to
+                # the first statement.
+                elif action == "repeat":
+
+                    count, arg = arg
+                    loop_end = target - arg
+                    duration = loop_end - loop_start
+
+                    if duration <= 0:
+                        raise Exception("ATL appears to be in an infinite loop.")
+
+                    # Figure how many durations can occur between the
+                    # start of the loop and now.
+                    new_repeats = int((target - loop_start) / duration)
+
+                    if count is not None:
+                        if repeats + new_repeats >= count:
+                            new_repeats = count - repeats
+                            loop_start += new_repeats * duration
+                            return "next", target - loop_start, None
+
+                    repeats += new_repeats
+                    loop_start = loop_start + new_repeats * duration
+                    start = loop_start
+                    index = 0
+                    child_state = None
+
+            if times:
+                time, tindex = times[0]
+                if time <= target:
+                    times.pop(0)
+                    
+                    index = tindex
+                    start = time
+                    child_state = None
+
+                    continue
+
+            return action, arg, pause
+
+    def visit(self):
+        return [ j for i in self.statements for j in i.visit() ]
+            
+# This can become one of four things:
+#
+# - A pause.
+# - An interpolation (which optionally can also reference other
+# blocks, as long as they're not time-dependent, and have the same
+# arity as the interpolation).
+# - A call to another block.
+# - A command to change the image, perhaps with a transition.
+#
+# We won't decide which it is until runtime, as we need the
+# values of the variables here.
+class RawMultipurpose(RawStatement):
+
+    warp_function = None
+    
+    def __init__(self, loc):
+
+        super(RawMultipurpose, self).__init__(loc)
+        
+        self.warper = None
+        self.duration = None
+        self.properties = [ ]
+        self.expressions = [ ]
+        self.splines = [ ]
+        self.revolution = None
+        self.circles = "0"
+        
+    def add_warper(self, name, duration, warp_function):
+        self.warper = name
+        self.duration = duration
+        self.warp_function = warp_function
+        
+    def add_property(self, name, exprs):
+        self.properties.append((name, exprs))
+
+    def add_expression(self, expr, with_clause):
+        self.expressions.append((expr, with_clause))
+
+    def add_revolution(self, revolution):
+        self.revolution = revolution
+        
+    def add_circles(self, circles):
+        self.circles = circles
+
+    def add_spline(self, name, exprs):
+        self.splines.append((name, exprs))
+        
+    def compile(self, ctx): #@ReservedAssignment
+
+        compiling(self.loc)
+        
+        # Figure out what kind of statement we have. If there's no
+        # interpolator, and no properties, than we have either a
+        # call, or a child statement.
+        if (self.warper is None and
+            self.warp_function is None and
+            not self.properties and
+            not self.splines and
+            len(self.expressions) == 1):
+
+            expr, withexpr = self.expressions[0]
+
+            child = ctx.eval(expr)
+            if withexpr:
+                transition = ctx.eval(withexpr)
+            else:
+                transition = None
+
+            if isinstance(child, (int, float)):
+                return Interpolation(self.loc, "pause", child, [ ], None, 0, [ ])
+                
+            if isinstance(child, ATLTransformBase):
+                child.compile()
+                return child.block
+
+            else:
+                return Child(self.loc, child, transition)
+
+        compiling(self.loc)
+
+        # Otherwise, we probably have an interpolation statement.
+
+        if self.warp_function:
+            warper = ctx.eval(self.warp_function)
+        else:            
+            warper = self.warper or "pause"
+
+            if warper not in warpers:
+                raise Exception("ATL Warper %s is unknown at runtime." % warper)
+
+        properties = [ ]
+
+        for name, expr in self.properties:
+            if name not in PROPERTIES:
+                raise Exception("ATL Property %s is unknown at runtime." % property)
+
+            value = ctx.eval(expr)
+            properties.append((name, value))
+
+        splines = [ ]
+            
+        for name, exprs in self.splines:
+            if name not in PROPERTIES:
+                raise Exception("ATL Property %s is unknown at runtime." % property)
+
+            values = [ ctx.eval(i) for i in exprs ]
+
+            splines.append((name, values))
+            
+        for expr, _with in self.expressions:
+            try:
+                value = ctx.eval(expr)
+            except:
+                raise Exception("Could not evaluate expression %r when compiling ATL." % expr)
+
+            if not isinstance(value, ATLTransformBase):
+                raise Exception("Expression %r is not an ATL transform, and so cannot be included in an ATL interpolation." % expr)
+
+            value.compile()
+
+            if value.properties is None:
+                raise Exception("ATL transform %r is too complicated to be included in interpolation." % expr)
+
+
+            properties.extend(value.properties)
+
+        duration = ctx.eval(self.duration)
+        circles = ctx.eval(self.circles)
+
+        return Interpolation(self.loc, warper, duration, properties, self.revolution, circles, splines)
+            
+    def predict(self, ctx):
+
+        for i, _j in self.expressions:
+            
+            try:
+                i = ctx.eval(i)
+            except:
+                continue
+
+            if isinstance(i, ATLTransformBase):
+                i.atl.predict(ctx)
+                return
+
+            try:
+                renpy.easy.predict(i)
+            except:
+                continue
+            
+# This lets us have an ATL transform as our child.
+class RawContainsExpr(RawStatement):
+
+    def __init__(self, loc, expr):
+
+        super(RawContainsExpr, self).__init__(loc)
+
+        self.expression = expr
+
+    def compile(self, ctx): #@ReservedAssignment
+        compiling(self.loc)
+        child = ctx.eval(self.expression)
+        return Child(self.loc, child, None)
+
+
+# This allows us to have multiple children, inside a Fixed.
+class RawChild(RawStatement):
+
+    def __init__(self, loc, child):
+
+        super(RawChild, self).__init__(loc)
+
+        self.children = [ child ]
+
+    def compile(self, ctx): #@ReservedAssignment
+        box = renpy.display.layout.MultiBox(layout='fixed')
+
+        for i in self.children:
+            box.add(renpy.display.motion.ATLTransform(i, context=ctx.context))
+
+        return Child(self.loc, box, None)
+
+    
+# This changes the child of this statement, optionally with a transition.
+class Child(Statement):
+
+    def __init__(self, loc, child, transition):
+
+        super(Child, self).__init__(loc)
+        
+        self.child = renpy.easy.displayable(child)
+        self.transition = transition
+
+    def execute(self, trans, st, state, event):
+
+        executing(self.loc)
+        
+        old_child = trans.raw_child
+
+        if (old_child is not None) and (old_child is not renpy.display.motion.null) and (self.transition is not None):
+            child = self.transition(old_widget=old_child,
+                                    new_widget=self.child)
+        else:
+            child = self.child
+
+        trans.set_child(child)
+        trans.raw_child = self.child
+
+        return "next", st, None
+
+    def visit(self):
+        return [ self.child ]
+    
+        
+# This causes interpolation to occur.
+class Interpolation(Statement):
+
+    def __init__(self, loc, warper, duration, properties, revolution, circles, splines):
+
+        super(Interpolation, self).__init__(loc)
+
+        self.warper = warper
+        self.duration = duration
+        self.properties = properties
+        self.splines = splines
+        
+        # The direction we revolve in: cw, ccw, or None.
+        self.revolution = revolution
+
+        # The number of complete circles we make.
+        self.circles = circles
+        
+    def execute(self, trans, st, state, event):
+
+        executing(self.loc)
+        
+        warper = warpers.get(self.warper, self.warper)
+        
+        if self.duration:
+            complete = min(1.0, st / self.duration)
+        else:
+            complete = 1.0
+
+        complete = warper(complete)
+
+        if state is None:
+
+            # Create a new transform state, and apply the property
+            # changes to it.
+            newts = renpy.display.motion.TransformState()
+            newts.take_state(trans.state)
+
+            for k, v in self.properties:
+                setattr(newts, k, v)
+
+            # Now, the things we change linearly are in the difference
+            # between the new and old states.
+            linear = trans.state.diff(newts)
+            
+            revolution = None
+            splines = [ ]
+
+            # Clockwise revolution.
+            if self.revolution is not None:
+
+                # Remove various irrelevant motions.
+                for i in [ 'xpos', 'ypos',
+                           'xanchor', 'yanchor',
+                           'xaround', 'yaround',
+                           'xanchoraround', 'yanchoraround',
+                           ]:
+
+                    linear.pop(i, None)
+
+                if newts.xaround is not None:
+
+                    # Ensure we rotate around the new point.
+                    trans.state.xaround = newts.xaround
+                    trans.state.yaround = newts.yaround
+                    trans.state.xanchoraround = newts.xanchoraround
+                    trans.state.yanchoraround = newts.yanchoraround
+
+                    # Get the start and end angles and radii.
+                    startangle = trans.state.angle
+                    endangle = newts.angle
+                    startradius = trans.state.radius
+                    endradius = newts.radius
+
+                    # Make sure the revolution is in the appropriate direction,
+                    # and contains an appropriate number of circles.
+
+                    if self.revolution == "clockwise":
+                        if endangle < startangle:
+                            startangle -= 360
+
+                        startangle -= self.circles * 360
+
+                    elif self.revolution == "counterclockwise":
+                        if endangle > startangle:
+                            startangle += 360
+
+                        startangle += self.circles * 360
+                        
+                    # Store the revolution.
+                    revolution = (startangle, endangle, startradius, endradius)
+
+            # Figure out the splines.
+            for name, values in self.splines:
+                splines.append((name, [ getattr(trans.state, name) ] + values))
+                    
+            state = (linear, revolution, splines)
+
+            # Ensure that we set things, even if they don't actually
+            # change from the old state.
+            for k, v in self.properties:
+                if k not in linear:
+                    setattr(trans.state, k, v)
+
+        else:
+            linear, revolution, splines = state
+
+        # Linearly interpolate between the things in linear.
+        for k, (old, new) in linear.items():
+            value = interpolate(complete, old, new, PROPERTIES[k])
+
+            setattr(trans.state, k, value)            
+            
+        # Handle the revolution.
+        if revolution is not None:
+            startangle, endangle, startradius, endradius = revolution
+            trans.state.angle = interpolate(complete, startangle, endangle, float)
+            trans.state.radius = interpolate(complete, startradius, endradius, float)
+
+            
+        # Handle any splines we might have.
+        for name, values in splines:
+            value = interpolate_spline(complete, values)
+            setattr(trans.state, name, value)
+            
+        if st >= self.duration:
+            return "next", st - self.duration, None
+        else:
+            if not self.properties and not self.revolution and not self.splines:
+                return "continue", state, self.duration - st
+            else:            
+                return "continue", state, 0
+
+
+# Implementation of the repeat statement.
+class RawRepeat(RawStatement):
+
+    def __init__(self, loc, repeats):
+
+        super(RawRepeat, self).__init__(loc)
+
+        self.repeats = repeats
+
+    def compile(self, ctx): #@ReservedAssignment
+
+        compiling(self.loc)
+
+        repeats = self.repeats
+
+        if repeats is not None:
+            repeats = ctx.eval(repeats)
+            
+        return Repeat(self.loc, repeats)
+
+class Repeat(Statement):
+
+    def __init__(self, loc, repeats):
+
+        super(Repeat, self).__init__(loc)
+
+        self.repeats = repeats
+
+    def execute(self, trans, st, state, event):
+        return "repeat", (self.repeats, st), 0
+
+
+# Parallel statement.
+
+class RawParallel(RawStatement):
+
+    def __init__(self, loc, block):
+
+        super(RawParallel, self).__init__(loc)
+        self.blocks = [ block ]
+
+    def compile(self, ctx): #@ReservedAssignment
+        return Parallel(self.loc, [i.compile(ctx) for i in self.blocks])
+
+    def predict(self, ctx):
+        for i in self.blocks:
+            i.predict(ctx)
+    
+        
+class Parallel(Statement):
+    
+    def __init__(self, loc, blocks):
+        super(Parallel, self).__init__(loc)
+        self.blocks = blocks
+
+    def execute(self, trans, st, state, event):
+
+        executing(self.loc)
+        
+        if state is None:
+            state = [ (i, None) for i in self.blocks ]
+
+        # The amount of time left after finishing this block.
+        left = [ ]
+
+        # The duration of the pause.
+        pauses = [ ]
+
+        # The new state structure.
+        newstate = [ ]
+        
+        for i, istate in state:
+            
+            action, arg, pause = i.execute(trans, st, istate, event)
+
+            if pause is not None:
+                pauses.append(pause)
+
+            if action == "continue":
+                newstate.append((i, arg))
+            elif action == "next":
+                left.append(arg)
+            elif action == "event":
+                return action, arg, pause
+                
+        if newstate:
+            return "continue", newstate, min(pauses)
+        else:
+            return "next", min(left), None
+
+    def visit(self):
+        return [ j for i in self.blocks for j in i.visit() ]
+
+
+# The choice statement.
+
+class RawChoice(RawStatement):
+
+    def __init__(self, loc, chance, block):
+        super(RawChoice, self).__init__(loc)
+
+        self.choices = [ (chance, block) ]
+
+    def compile(self, ctx): #@ReservedAssignment
+        compiling(self.loc)
+        return Choice(self.loc, [ (ctx.eval(chance), block.compile(ctx)) for chance, block in self.choices])
+
+    def predict(self, ctx):
+        for _i, j in self.choices:
+            j.predict(ctx)
+
+class Choice(Statement):
+
+    def __init__(self, loc, choices):
+
+        super(Choice, self).__init__(loc)
+
+        self.choices = choices
+
+    def execute(self, trans, st, state, event):
+
+        executing(self.loc)
+        
+        if state is None:
+
+            total = 0
+            for chance, choice in self.choices:
+                total += chance
+
+            n = random.uniform(0, total)
+
+            for chance, choice in self.choices:
+                if n < chance:
+                    break
+                n -= chance
+
+            cstate = None
+
+        else:
+            choice, cstate = state
+
+        action, arg, pause = choice.execute(trans, st, cstate, event)
+
+        if action == "continue":
+            return "continue", (choice, arg), pause
+        else:
+            return action, arg, None
+
+    def visit(self):
+        return [ j for i in self.choices for j in i[1].visit() ]
+
+        
+# The Time statement.
+
+class RawTime(RawStatement):
+
+    def __init__(self, loc, time):
+
+        super(RawTime, self).__init__(loc)
+        self.time = time
+
+    def compile(self, ctx): #@ReservedAssignment
+        compiling(self.loc)
+        return Time(self.loc, ctx.eval(self.time))
+
+class Time(Statement):
+
+    def __init__(self, loc, time):
+        super(Time, self).__init__(loc)
+
+        self.time = time
+
+    def execute(self, trans, st, state, event):
+        return "continue", None, None
+        
+
+# The On statement.
+
+class RawOn(RawStatement):
+
+    def __init__(self, loc, name, block):
+        super(RawOn, self).__init__(loc)
+
+        self.handlers = { name : block }
+
+    def compile(self, ctx): #@ReservedAssignment
+
+        compiling(self.loc)
+
+        handlers = { }
+
+        for k, v in self.handlers.items():
+            handlers[k] = v.compile(ctx)
+
+        return On(self.loc, handlers)
+
+    def predict(self, ctx):
+        for i in self.handlers.values():
+            i.predict(ctx)
+
+class On(Statement):
+
+    def __init__(self, loc, handlers):
+        super(On, self).__init__(loc)
+
+        self.handlers = handlers
+    
+    def execute(self, trans, st, state, event):
+
+        executing(self.loc)
+
+        # If it's our first time through, start in the start state.
+        if state is None:
+            name, start, cstate = ("start", st, None)
+        else:
+            name, start, cstate = state
+
+        # If we have an external event, and we have a handler for it,
+        # handle it.
+        if event in self.handlers:
+
+            # Do not allow people to abort the hide handler with another
+            # event.
+            if name != "hide":
+                name = event
+                start = st
+                cstate = None
+
+                
+        while True:
+
+            # If we don't have a handler, return until we change event.
+            if name not in self.handlers:
+                return "continue", (name, start, cstate), None
+            
+            action, arg, pause = self.handlers[name].execute(trans, st - start, cstate, event)
+
+            # If we get a continue, save our state.
+            if action == "continue":
+                
+                # If it comes from a hide block, indicate that.
+                if name == "hide" or name == "replaced":
+                    trans.hide_response = False
+                    trans.replaced_response = False
+                    
+                return "continue", (name, start, arg), pause
+
+            # If we get a next, then try going to the default
+            # event, unless we're already in default, in which case we
+            # go to None.
+            elif action == "next":
+                if name == "default" or name == "hide" or name == "replaced":
+                    name = None
+                else:
+                    name = "default"
+
+                start = st - arg
+                cstate = None
+
+                continue
+
+            # If we get an event, then either handle it if we can, or
+            # pass it up the stack if we can't.
+            elif action == "event":
+
+                name, arg = arg
+
+                if name in self.handlers:
+                    start = max(st - arg, st - 30)
+                    cstate = None
+                    continue
+
+                return "event", (name, arg), None
+
+    def visit(self):
+        return [ j for i in self.handlers.values() for j in i.visit() ]
+
+
+# Event statement.
+            
+class RawEvent(RawStatement):
+
+    def __init__(self, loc, name):
+        super(RawEvent, self).__init__(loc)
+
+        self.name = name
+
+    def compile(self, ctx): #@ReservedAssignment
+        return Event(self.loc, self.name)
+
+    
+class Event(Statement):
+
+    def __init__(self, loc, name):
+        super(Event, self).__init__(loc)
+
+        self.name = name
+
+    def execute(self, trans, st, state, event):
+        return "event", (self.name, st), None
+    
+
+class RawFunction(RawStatement):
+
+    def __init__(self, loc, expr):
+        super(RawFunction, self).__init__(loc)
+
+        self.expr = expr
+
+    def compile(self, ctx): #@ReservedAssignment
+        compiling(self.loc)
+        return Function(self.loc, ctx.eval(self.expr))
+
+    
+class Function(Statement):
+    
+    def __init__(self, loc, function):
+        super(Function, self).__init__(loc)
+
+        self.function = function
+
+    def execute(self, trans, st, state, event):
+        fr = self.function(trans, st, trans.at)
+        
+        if fr is not None:
+            return "continue", None, fr
+        else:
+            return "next", 0, None
+    
+    
+# This parses an ATL block.
+def parse_atl(l):
+
+    l.advance()
+    block_loc = l.get_location()
+
+    statements = [ ]
+
+    animation = False
+    
+    while not l.eob:
+
+        loc = l.get_location()
+        
+        if l.keyword('repeat'):
+
+            repeats = l.simple_expression()
+            statements.append(RawRepeat(loc, repeats))
+
+        elif l.keyword('block'):
+            l.require(':')
+            l.expect_eol()
+            l.expect_block('block')
+
+            block = parse_atl(l.subblock_lexer())            
+            statements.append(block)
+
+        elif l.keyword('contains'):
+
+            expr = l.simple_expression()
+
+            if expr:
+
+                l.expect_noblock('contains expression')
+                statements.append(RawContainsExpr(loc, expr))
+
+            else:
+
+                l.require(':')
+                l.expect_eol()
+                l.expect_block('contains')
+
+                block = parse_atl(l.subblock_lexer())            
+                statements.append(RawChild(loc, block))
+
+        elif l.keyword('parallel'):
+            l.require(':')
+            l.expect_eol()
+            l.expect_block('parallel')
+            
+            block = parse_atl(l.subblock_lexer())
+            statements.append(RawParallel(loc, block))
+
+        elif l.keyword('choice'):
+
+            chance = l.simple_expression()
+            if not chance:
+                chance = "1.0"
+
+            l.require(':')
+            l.expect_eol()
+            l.expect_block('choice')
+            
+            block = parse_atl(l.subblock_lexer())
+            statements.append(RawChoice(loc, chance, block))
+
+        elif l.keyword('on'):
+
+            name = l.require(l.word)
+
+            l.require(':')
+            l.expect_eol()
+            l.expect_block('on')
+            
+            block = parse_atl(l.subblock_lexer())
+            statements.append(RawOn(loc, name, block))
+
+        elif l.keyword('time'):
+            time = l.require(l.simple_expression)
+            l.expect_noblock('time')
+
+            statements.append(RawTime(loc, time))
+
+        elif l.keyword('function'):
+            expr = l.require(l.simple_expression)
+            l.expect_noblock('function')
+
+            statements.append(RawFunction(loc, expr))
+
+        elif l.keyword('event'):
+            name = l.require(l.word)
+            l.expect_noblock('event')
+
+            statements.append(RawEvent(loc, name))
+
+        elif l.keyword('pass'):
+            l.expect_noblock('pass')
+            statements.append(None)
+
+        elif l.keyword('animation'):
+            l.expect_noblock('animation')
+            animation = True
+            
+        else:
+
+            # If we can't assign it it a statement more specifically,
+            # we try to parse it into a RawMultipurpose. That will
+            # then be turned into another statement, as appropriate.
+            
+            # The RawMultipurpose we add things to.
+            rm = renpy.atl.RawMultipurpose(loc)
+
+            # Is the last clause an expression?
+            last_expression = False
+
+            # Is this clause an expression?
+            this_expression = False
+            
+            # First, look for a warper.
+            cp = l.checkpoint()
+            warper = l.name()
+
+            
+            if warper in warpers:
+                duration = l.require(l.simple_expression)
+                warp_function = None
+
+            elif warper == "warp":
+                
+                warper = None
+                warp_function = l.require(l.simple_expression)
+                duration = l.require(l.simple_expression)
+                
+            else:
+                l.revert(cp)
+
+                warper = None
+                warp_function = None
+                duration = "0"
+                
+            rm.add_warper(warper, duration, warp_function)
+
+            # Now, look for properties and simple_expressions.
+            while True:
+
+                # Update expression status.
+                last_expression = this_expression
+                this_expression = False
+
+                if l.keyword('pass'):
+                    continue
+                
+                # Parse revolution keywords.
+                if l.keyword('clockwise'):
+                    rm.add_revolution('clockwise')
+                    continue
+
+                if l.keyword('counterclockwise'):
+                    rm.add_revolution('counterclockwise')
+                    continue
+
+                if l.keyword('circles'):
+                    expr = l.require(l.simple_expression)
+                    rm.add_circles(expr)
+
+                # Try to parse a property. 
+                cp = l.checkpoint()
+                
+                prop = l.name()
+
+                if prop in PROPERTIES:
+
+                    expr = l.require(l.simple_expression)
+
+                    # We either have a property or a spline. It's the
+                    # presence of knots that determine which one it is.
+
+                    knots = [ ]
+                    
+                    while l.keyword('knot'):
+                        knots.append(l.require(l.simple_expression))
+
+                    if knots:
+                        knots.append(expr)
+                        rm.add_spline(prop, knots)
+                    else:
+                        rm.add_property(prop, expr)
+
+                    continue
+                    
+                # Otherwise, try to parse it as a simple expressoon,
+                # with an optional with clause.
+
+                l.revert(cp)
+
+                expr = l.simple_expression()
+
+                if not expr:
+                    break
+
+                if last_expression:
+                    l.error('ATL statement contains two expressions in a row; is one of them a misspelled property? If not, separate them with pass.')
+
+                this_expression = True
+                    
+                if l.keyword("with"):
+                    with_expr = l.require(l.simple_expression)
+                else:
+                    with_expr = None
+
+                rm.add_expression(expr, with_expr)
+
+            l.expect_noblock('ATL')
+                
+            statements.append(rm)
+            
+            
+        if l.eol():
+            l.advance()
+            continue
+
+        l.require(",", "comma or end of line")
+
+        
+    # Merge together statements that need to be merged together.
+
+    merged = [ ]
+    old = None
+
+    for new in statements:
+
+        if isinstance(old, RawParallel) and isinstance(new, RawParallel):
+            old.blocks.extend(new.blocks)
+            continue
+
+        elif isinstance(old, RawChoice) and isinstance(new, RawChoice):
+            old.choices.extend(new.choices)
+            continue
+
+        elif isinstance(old, RawChild) and isinstance(new, RawChild):
+            old.children.extend(new.children)
+            continue
+
+        elif isinstance(old, RawOn) and isinstance(new, RawOn):
+            old.handlers.update(new.handlers)
+            continue
+
+        # None is a pause statement, which gets skipped, but also
+        # prevents things from combining.
+        elif new is None:
+            old = new
+            continue
+        
+        merged.append(new)
+        old = new
+
+    return RawBlock(block_loc, merged, animation)
diff --git a/unrpyc/renpy/display/__init__.py b/unrpyc/renpy/display/__init__.py
new file mode 100644
index 0000000..055dad6
--- /dev/null
+++ b/unrpyc/renpy/display/__init__.py
@@ -0,0 +1,39 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy.log
+
+# The draw object through which all drawing is routed. This object
+# contains all of the distinction between the software and GL
+# renderers.
+draw = None
+
+# The interface object.
+interface = None
+
+# Should we disable imagedissolve-type transitions?
+less_imagedissolve = False
+
+# Logs we use.
+log = renpy.log.open("log", developer=False, append=False)
+ic_log = renpy.log.open("image_cache", developer=True, append=False)
+to_log = renpy.log.open("text_overflow", developer=True, append=True)
+
diff --git a/unrpyc/renpy/display/accelerator.pyx b/unrpyc/renpy/display/accelerator.pyx
new file mode 100644
index 0000000..8c4172c
--- /dev/null
+++ b/unrpyc/renpy/display/accelerator.pyx
@@ -0,0 +1,296 @@
+#cython: profile=False
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy
+import math
+from renpy.display.render cimport Render, Matrix2D, render
+
+
+################################################################################
+# Surface copying
+################################################################################
+
+from pygame cimport *
+
+def nogil_copy(src, dest):
+    """
+    Does a gil-less blit of src to dest, with minimal locking.
+    """
+    
+    cdef SDL_Surface *src_surf
+    cdef SDL_Surface *dst_surf
+
+    src_surf = PySurface_AsSurface(src)
+    dest_surf = PySurface_AsSurface(dest)
+
+    old_alpha = src_surf.flags & SDL_SRCALPHA
+    
+    if old_alpha:
+        SDL_SetAlpha(src_surf, 0, 255)
+    
+    with nogil:
+        SDL_BlitSurface(src_surf, NULL, dest_surf, NULL)
+        
+    if old_alpha:
+        SDL_SetAlpha(src_surf, SDL_SRCALPHA, 255)
+    
+
+
+################################################################################
+# Transform render function
+################################################################################
+
+cdef Matrix2D IDENTITY
+IDENTITY = renpy.display.render.IDENTITY
+
+# This file contains implementations of methods of classes that
+# are found in other files, for performance reasons.
+
+def transform_render(self, widtho, heighto, st, at):
+
+    cdef double rxdx, rxdy, rydx, rydy
+    cdef double cosa, sina
+    cdef double xo, x1, x2, x3, px
+    cdef double yo, y1, y2, y3, py
+    cdef float zoom, xzoom, yzoom
+    cdef double cw, ch, nw, nh
+    cdef Render rv, cr
+    cdef double angle
+    cdef double alpha
+    cdef double width = widtho
+    cdef double height = heighto
+    
+    # Should we perform clipping?
+    clipping = False
+
+    # Prevent time from ticking backwards, as can happen if we replace a 
+    # transform but keep its state.
+    if st + self.st_offset <= self.st:
+        self.st_offset = self.st - st
+    if at + self.at_offset <= self.at:
+        self.at_offset = self.at - at
+
+    self.st = st = st + self.st_offset
+    self.at = at = at + self.at_offset
+
+    # Update the state.
+    self.update_state()
+
+    # Render the child.
+    child = self.child
+    
+    if child is None:
+        raise Exception("Transform does not have a child.")
+
+    state = self.state
+
+    if state.size:
+        widtho, heighto = state.size
+
+    cr = render(child, widtho, heighto, st - self.child_st_base, at)
+
+    width = cr.width
+    height = cr.height
+
+    self.child_size = width, height
+
+    # The reverse matrix.
+    rxdx = 1
+    rxdy = 0
+    rydx = 0
+    rydy = 1
+
+    xo = 0
+    yo = 0
+    
+    # Cropping.
+    crop = state.crop
+    if (state.corner1 is not None) and (crop is None) and (state.corner2 is not None):
+        x1, y1 = state.corner1
+        x2, y2 = state.corner2
+
+        if x1 > x2:
+            x3 = x1
+            x1 = x2
+            x2 = x3
+        if y1 > y2:
+            y3 = y1
+            y1 = y2
+            y2 = y3
+
+        crop = (x1, y1, x2-x1, y2-y1)
+
+    if crop is not None:
+
+        negative_xo, negative_yo, width, height = crop
+
+        if state.rotate:
+            clipcr = Render(width, height)
+            clipcr.subpixel_blit(cr, (-negative_xo, -negative_yo))
+            clipcr.clipping = True
+            cr = clipcr
+        else:
+            xo = -negative_xo
+            yo = -negative_yo
+            clipping = True
+
+    # Size.
+    size = state.size 
+    if (size is not None) and (size != (width, height)):
+        nw, nh = size
+        xzoom = 1.0 * nw / width
+        yzoom = 1.0 * nh / height
+
+        rxdx = xzoom
+        rydy = yzoom
+
+        xo *= xzoom
+        yo *= yzoom
+
+        width, height = size
+
+    # zoom
+    zoom = state.zoom
+    xzoom = zoom * <double> state.xzoom
+    yzoom = zoom * <double> state.yzoom
+
+    if xzoom != 1:
+
+        rxdx *= xzoom
+
+        if xzoom < 0:
+            width *= -xzoom
+        else:
+            width *= xzoom
+
+        xo *= xzoom
+        # origin corrections for flipping
+        if xzoom < 0:
+            xo += width
+
+    if yzoom != 1:
+
+        rydy *= yzoom
+
+        if yzoom < 0:
+            height *= -yzoom
+        else:
+            height *= yzoom
+
+        yo *= yzoom
+        # origin corrections for flipping
+        if yzoom < 0:
+            yo += height
+
+    # Rotation.
+    rotate = state.rotate
+    if rotate is not None:
+
+        cw = width
+        ch = height
+
+        angle = rotate * 3.1415926535897931 / 180
+
+        cosa = math.cos(angle)
+        sina = math.sin(angle)
+
+        # reverse = Matrix2D(xdx, xdy, ydx, ydy) * reverse
+
+        # We know that at this point, rxdy and rydx are both 0, so
+        # we can simplify these formulae a bit.            
+        rxdy = rydy * -sina
+        rydx = rxdx * sina
+        rxdx *= cosa
+        rydy *= cosa
+
+        # first corner point (changes with flipping)
+        px = cw / 2.0
+        if xzoom < 0:
+            px = -px
+        py = ch / 2.0
+        if yzoom < 0:
+            py = -py
+        
+        if state.rotate_pad:
+            width = height = math.hypot(cw, ch)
+
+            xo = -px * cosa + py * sina
+            yo = -px * sina - py * cosa
+
+        else:
+            xo = -px * cosa + py * sina
+            yo = -px * sina - py * cosa
+
+            x2 = -px * cosa - py * sina
+            y2 = -px * sina + py * cosa
+
+            x3 =  px * cosa - py * sina
+            y3 =  px * sina + py * cosa
+
+            x4 =  px * cosa + py * sina
+            y4 =  px * sina - py * cosa
+
+            width = max(xo, x2, x3, x4) - min(xo, x2, x3, x4) 
+            height = max(yo, y2, y3, y4) - min(yo, y2, y3, y4) 
+
+        xo += width / 2.0
+        yo += height / 2.0
+        
+    alpha = state.alpha
+
+    rv = Render(width, height)
+
+    # Default case - no transformation matrix.
+    if rxdx == 1 and rxdy == 0 and rydx == 0 and rydy == 1:
+        self.forward = IDENTITY
+        self.reverse = IDENTITY
+
+    else:
+
+        self.reverse = rv.reverse = Matrix2D(rxdx, rxdy, rydx, rydy)
+
+        inv_det = rxdx * rydy - rxdy * rydx
+
+        if not inv_det:
+            self.forward = rv.forward = Matrix2D(0, 0, 0, 0)
+        else:
+            self.forward = rv.forward = Matrix2D(
+                rydy / inv_det,
+                -rxdy / inv_det,
+                -rydx / inv_det, 
+                rxdx / inv_det)
+
+    rv.alpha = alpha
+    rv.clipping = clipping
+
+    pos = (xo, yo)
+    
+    if state.subpixel:
+        rv.subpixel_blit(cr, pos)
+    else:
+        rv.blit(cr, pos)
+
+    self.offsets = [ pos ]
+    self.render_size = (width, height)
+
+    return rv
+
diff --git a/unrpyc/renpy/display/anim.py b/unrpyc/renpy/display/anim.py
new file mode 100644
index 0000000..de1398a
--- /dev/null
+++ b/unrpyc/renpy/display/anim.py
@@ -0,0 +1,634 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains support for state-machine controlled animations.
+
+import renpy.display
+import random
+
+class State(object):
+    """
+    This creates a state that can be used in a SMAnimation.
+    """
+
+
+    def __init__(self, name, image, *atlist, **properties):
+        """
+        @param name: A string giving the name of this state.
+
+        @param image: The displayable that is shown to the user while
+        we are in (entering) this state. For convenience, this can
+        also be a string or tuple, which is interpreted with Image.
+
+        image should be None when this State is used with motion,
+        to indicate that the image will be replaced with the child of
+        the motion.
+
+        @param atlist: A list of functions to call on the image. (In
+        general, if something can be used in an at clause, it can be
+        used here as well.)
+
+        If any keyword arguments are given, they are used to construct a
+        Position object, that modifies the position of the image.
+        """
+
+        if image and not isinstance(image, renpy.display.core.Displayable):
+            image = renpy.easy.displayable(image)
+
+        self.name = name
+        self.image = image
+        self.atlist = atlist
+        self.properties = properties
+
+
+    def add(self, sma):
+        sma.states[self.name] = self
+
+    def get_image(self):
+        rv = self.image
+
+        for i in self.atlist:
+            rv = i(rv)
+
+        if self.properties:
+            rv = renpy.display.layout.Position(rv, **self.properties)
+
+        return rv
+
+    def motion_copy(self, child):
+
+        if self.image is not None:
+            child = self.image
+
+        return State(self.name, child, *self.atlist)
+    
+
+class Edge(object):
+    """
+    This creates an edge that can be used with a SMAnimation.
+    """
+
+    def __init__(self, old, delay, new, trans=None, prob=1):
+        """
+        @param old: The name (a string) of the state that this transition is from.
+
+        @param delay: The number of seconds that this transition takes.
+
+        @param new: The name (a string) of the state that this transition is to.
+
+        @param trans: The transition that will be used to show the
+        image found in the new state. If None, the image is show
+        immediately.
+
+        When used with an SMMotion, the transition should probably be
+        move.
+
+        @param prob: The number of times this edge is added. This can
+        be used to make a transition more probable then others. For
+        example, if one transition out of a state has prob=5, and the
+        other has prob=1, then the one with prob=5 will execute 5/6 of
+        the time, while the one with prob=1 will only occur 1/6 of the
+        time. (Don't make this too large, as memory use is proportional to
+        this value.)
+        """
+
+        self.old = old
+        self.delay = delay
+        self.new = new
+        self.trans = trans
+        self.prob = prob
+
+    def add(self, sma):
+        for _i in range(0, self.prob):
+            sma.edges.setdefault(self.old, []).append(self)
+
+
+class SMAnimation(renpy.display.core.Displayable):
+    """
+    This creates a state-machine animation. Such an animation is
+    created by randomly traversing the edges between states in a
+    defined state machine. Each state corresponds to an image shown to
+    the user, with the edges corresponding to the amount of time an
+    image is shown, and the transition it is shown with.
+
+    Images are shown, perhaps with a transition, when we are
+    transitioning into a state containing that image.
+    """
+    
+    def __init__(self, initial, *args, **properties):
+        """
+        @param initial: The name (a string) of the initial state we
+        start in.
+
+        @param showold: If the keyword parameter showold is True, then
+        the old image is shown instead of the new image when in an
+        edge.
+
+        @param anim_timebase: If True, we use the animation
+        timebase. If False, we use the displayable timebase.
+
+        This accepts as additional arguments the anim.State and
+        anim.Edge objects that are used to make up this state
+        machine.
+        """
+
+        if 'delay' in properties:
+            self.delay = properties['delay']
+            del properties['delay']
+        else:
+            self.delay = None
+
+        if 'showold' in properties:
+            self.showold = properties['showold']
+            del properties['showold']
+        else:
+            self.showold = False
+
+        if 'anim_timebase' in properties:
+            self.anim_timebase = properties['anim_timebase']
+            del properties['anim_timebase']
+        else:
+            self.anim_timebase = True
+
+        super(SMAnimation, self).__init__(**properties)
+
+        self.properties = properties
+
+        # The initial state.
+        self.initial = initial
+
+        # A map from state name to State object.
+        self.states = { }
+
+        # A map from state name to list of Edge objects.
+        self.edges = { }
+
+        for i in args:
+            i.add(self)
+
+        # The time at which the current edge started. If None, will be
+        # set to st by render.
+        self.edge_start = None
+
+        # A cache for what the current edge looks like when rendered.
+        self.edge_cache = None
+
+        # The current edge.
+        self.edge = None
+
+        # The state we're in.
+        self.state = None
+
+    def visit(self):
+        return [ i.image for i in self.states.values() ]
+
+    def pick_edge(self, state):
+        """
+        This randomly picks an edge out of the given state, if
+        one exists. It updates self.edge if a transition has
+        been selected, or returns None if none can be found. It also
+        updates self.image to be the new image on the selected edge.
+        """
+
+        if state not in self.edges:
+            self.edge = None
+            return
+
+        edges = self.edges[state]
+        self.edge = random.choice(edges)
+        self.state = self.edge.new
+        
+    def update_cache(self):
+        """
+        Places the correct Displayable into the edge cache, based on
+        what is contained in the given edge. This takes into account
+        the old and new states, and any transition that is present.
+        """
+
+
+        if self.edge.trans:
+            im = self.edge.trans(old_widget=self.states[self.edge.old].get_image(),
+                                 new_widget=self.states[self.edge.new].get_image())
+        elif self.showold:
+            im = self.states[self.edge.old].get_image()
+        else:
+            im = self.states[self.edge.new].get_image()
+
+        self.edge_cache = im
+
+    def get_placement(self):
+
+        if self.edge_cache:
+            return self.edge_cache.get_placement()
+
+        if self.state:
+            return self.states[self.state].get_image().get_placement()
+
+        return super(SMAnimation, self).get_placement()
+
+    def render(self, width, height, st, at):
+
+        if self.anim_timebase:
+            t = at
+        else:
+            t = st
+
+        if self.edge_start is None or t < self.edge_start:
+            self.edge_start = t
+            self.edge_cache = None
+            self.pick_edge(self.initial)
+
+        while self.edge and t > self.edge_start + self.edge.delay:
+            self.edge_start += self.edge.delay
+            self.edge_cache = None
+            self.pick_edge(self.edge.new)
+
+        # If edge is None, then we have a permanent, static picture. Deal
+        # with that.
+
+        if not self.edge:
+            im = renpy.display.render.render(self.states[self.state].get_image(),
+                                             width, height,
+                                             st - self.edge_start, at)
+
+
+        # Otherwise, we have another edge.
+
+        else:
+            if not self.edge_cache:
+                self.update_cache()
+
+            im = renpy.display.render.render(self.edge_cache, width, height, t - self.edge_start, at)
+
+            if not renpy.game.less_updates:
+                renpy.display.render.redraw(self.edge_cache, self.edge.delay - (t - self.edge_start))
+
+
+        iw, ih = im.get_size()
+
+        rv = renpy.display.render.Render(iw, ih)
+        rv.blit(im, (0, 0))
+
+        return rv
+    
+    def __call__(self, child=None, new_widget=None, old_widget=None):
+        """
+        Used when this SMAnimation is used as a SMMotion. This creates
+        a duplicate of the animation, with all states containing None
+        as the image having that None replaced with the image that is provided here.
+        """
+
+        if child is None:
+            child = new_widget
+
+        args = [ ]
+
+        for state in self.states.values():
+            args.append(state.motion_copy(child))
+
+        for edges in self.edges.values():
+            args.extend(edges)
+
+        return SMAnimation(self.initial, delay=self.delay, *args, **self.properties)
+
+
+# class Animation(renpy.display.core.Displayable):
+#     """
+#     A Displayable that draws an animation, which is a series of images
+#     that are displayed with time delays between them.
+#     """
+
+#     def __init__(self, *args, **properties):
+#         """
+#         Odd (first, third, fifth, etc.) arguments to Animation are
+#         interpreted as image filenames, while even arguments are the
+#         time to delay between each image. If the number of arguments
+#         is odd, the animation will stop with the last image (well,
+#         actually delay for a year before looping). Otherwise, the
+#         animation will restart after the final delay time.
+
+#         @param anim_timebase: If True, the default, use the animation
+#         timebase. Otherwise, use the displayable timebase.
+#         """
+
+#         properties.setdefault('style', 'animation')
+#         self.anim_timebase = properties.pop('anim_timebase', True)
+
+#         super(Animation, self).__init__(**properties)
+
+#         self.images = [ ]
+#         self.delays = [ ]
+
+#         for i, arg in enumerate(args):
+
+#             if i % 2 == 0:
+#                 self.images.append(renpy.easy.displayable(arg))
+#             else:
+#                 self.delays.append(arg)
+
+#         if len(self.images) > len(self.delays):
+#             self.delays.append(365.25 * 86400.0) # One year, give or take.
+                
+#     def render(self, width, height, st, at):
+
+#         if self.anim_timebase:
+#             t = at % sum(self.delays)
+#         else:
+#             t = st % sum(self.delays)
+
+#         for image, delay in zip(self.images, self.delays):
+#             if t < delay:
+#                 renpy.display.render.redraw(self, delay - t)
+
+#                 im = renpy.display.render.render(image, width, height, t, at)
+#                 width, height = im.get_size()
+#                 rv = renpy.display.render.Render(width, height)
+#                 rv.blit(im, (0, 0))
+
+#                 return rv
+            
+#             else:
+#                 t = t - delay
+
+#     def visit(self):
+#         return self.images
+
+def Animation(*args, **kwargs):
+    newargs = [ ]
+
+    for i, a in enumerate(args):
+        newargs.append(a)
+        if i % 2 == 1:
+            newargs.append(None)
+
+    return TransitionAnimation(*newargs, **kwargs)
+    
+
+class TransitionAnimation(renpy.display.core.Displayable):
+    """
+    A displayable that draws an animation with each frame separated
+    by a transition.
+    """
+
+    def __init__(self, *args, **properties):
+        """
+        This takes arguments such that the 1st, 4th, 7th, ...
+        arguments are displayables, the 2nd, 5th, 8th, ... on arguments
+        are times, and the 3rd, 6th, 9th, ... are transitions.
+
+        This displays the first displayable for the given time, then
+        transitions to the second displayable using the given
+        transition, and shows it for the given time (the time of the
+        transition is taken out of the time the frame is shown), and
+        so on.
+
+        The last argument may be a displayable (in which case that
+        displayable is used to transition back to the first frame), or
+        a displayable (which is shown forever).
+
+        There is one keyword argument, apart from the style properties:
+        
+        @param anim_timebase: If True, the default, use the animation
+        timebase. Otherwise, use the displayable timebase.
+        """
+
+        properties.setdefault('style', 'animation')
+        self.anim_timebase = properties.pop('anim_timebase', True)
+
+        super(TransitionAnimation, self).__init__(**properties)
+
+        images = [ ]
+        delays = [ ]
+        transitions = [ ]
+        
+        for i, arg in enumerate(args):
+
+            if i % 3 == 0:
+                images.append(renpy.easy.displayable(arg))
+            elif i % 3 == 1:
+                delays.append(arg)
+            else:
+                transitions.append(arg)
+
+        if len(images) > len(delays):
+            delays.append(365.25 * 86400.0) # One year, give or take.
+        if len(images) > len(transitions):
+            transitions.append(None)
+
+        self.images = images
+        self.prev_images = [ images[-1] ] + images[:-1]
+        self.delays = delays
+        self.transitions = [ transitions[-1] ] + transitions[:-1]
+            
+            
+    def render(self, width, height, st, at):
+
+        if self.anim_timebase:
+            orig_t = at
+        else:
+            orig_t = st
+
+        t = orig_t % sum(self.delays)
+            
+        for image, prev, delay, trans in zip(self.images, self.prev_images, self.delays, self.transitions):
+            if t < delay:
+                if not renpy.game.less_updates:
+                    renpy.display.render.redraw(self, delay - t)
+
+                if trans and orig_t >= self.delays[0]:
+                    image = trans(old_widget=prev, new_widget=image)
+                
+                im = renpy.display.render.render(image, width, height, t, at)
+                width, height = im.get_size()
+                rv = renpy.display.render.Render(width, height)
+                rv.blit(im, (0, 0))
+
+                return rv
+            
+            else:
+                t = t - delay
+
+    def visit(self):
+        return self.images
+
+class Blink(renpy.display.core.Displayable):
+    """
+    """
+
+    def __init__(self, image, on=0.5, off=0.5, rise=0.5, set=0.5, #@ReservedAssignment
+                 high=1.0, low=0.0, offset=0.0, anim_timebase=False, **properties):
+
+        """
+        This takes as an argument an image or widget, and blinks that image
+        by varying its alpha. The sequence of phases is
+        on - set - off - rise - on - ... All times are given in seconds, all
+        alphas are fractions between 0 and 1.
+
+        @param image: The image or widget that will be blinked.
+
+        @param on: The amount of time the widget spends on, at high alpha.
+
+        @param off: The amount of time the widget spends off, at low alpha.
+
+        @param rise: The amount time the widget takes to ramp from low to high alpha.
+
+        @param set: The amount of time the widget takes to ram from high to low.
+
+        @param high: The high alpha.
+
+        @param low: The low alpha.
+
+        @param offset: A time offset, in seconds. Use this to have a
+        blink that does not start at the start of the on phase.
+
+        @param anim_timebase: If True, use the animation timebase, if false, the displayable timebase.
+        """
+        
+        super(Blink, self).__init__(**properties)
+
+        self.image = renpy.easy.displayable(image)
+        self.on = on
+        self.off = off
+        self.rise = rise
+        self.set = set
+        self.high = high
+        self.low = low
+        self.offset = offset
+        self.anim_timebase = anim_timebase
+
+        self.cycle = on + set + off + rise
+
+
+    def visit(self):
+        return [ self.image ]
+
+    def render(self, height, width, st, at):
+
+        if self.anim_timebase:
+            t = at
+        else:
+            t = st
+
+        time = (self.offset + t) % self.cycle
+        alpha = self.high
+
+        if 0 <= time < self.on:
+            delay = self.on - time
+            alpha = self.high
+
+        time -= self.on
+
+        if 0 <= time < self.set:
+            delay = 0            
+            frac = time / self.set
+            alpha = self.low * frac + self.high * (1.0 - frac)
+
+        time -= self.set
+
+        if 0 <= time < self.off:
+            delay = self.off - time
+            alpha = self.low
+
+        time -= self.off
+
+        if 0 <= time < self.rise:
+            delay = 0
+            frac = time / self.rise 
+            alpha = self.high * frac + self.low * (1.0 - frac)
+
+
+        rend = renpy.display.render.render(self.image, height, width, st, at)
+        w, h = rend.get_size()
+        rv = renpy.display.render.Render(w, h)
+
+        rv.blit(rend, (0, 0))
+        rv.alpha = alpha
+        
+        if not renpy.game.less_updates:
+            renpy.display.render.redraw(self, delay)
+
+        return rv
+
+
+
+def Filmstrip(image, framesize, gridsize, delay, frames=None, loop=True, **properties):
+    """
+    This creates an animation from a single image. This image
+    must consist of a grid of frames, with the number of columns and
+    rows in the grid being taken from gridsize, and the size of each
+    frame in the grid being taken from framesize. This takes frames
+    and sticks them into an Animation, with the given delay between
+    each frame. The frames are taken by going from left-to-right
+    across the first row, left-to-right across the second row, and
+    so on until all frames are consumed, or a specified number of
+    frames are taken.
+
+    @param image: The image that the frames must be taken from.
+
+    @param framesize: A (width, height) tuple giving the size of
+    each of the frames in the animation.
+
+    @param gridsize: A (columns, rows) tuple giving the number of
+    columns and rows in the grid.
+
+    @param delay: The delay, in seconds, between frames.
+
+    @param frames: The number of frames in this animation. If None,
+    then this defaults to colums * rows frames, that is, taking
+    every frame in the grid.
+
+    @param loop: If True, loop at the end of the animation. If False,
+    this performs the animation once, and then stops.
+
+    Other keyword arguments are as for anim.SMAnimation.
+    """
+
+    width, height = framesize
+    cols, rows = gridsize
+
+    if frames is None:
+        frames = cols * rows
+
+    i = 0
+
+    # Arguments to Animation
+    args = [ ]
+
+    for r in range(0, rows):
+        for c in range(0, cols):
+
+            x = c * width
+            y = r * height
+
+            args.append(renpy.display.im.Crop(image, x, y, width, height))
+            args.append(delay)
+
+            i += 1
+            if i == frames:
+                break
+
+        if i == frames:
+            break
+            
+    if not loop:
+        args.pop()
+
+    return Animation(*args, **properties)
diff --git a/unrpyc/renpy/display/behavior.py b/unrpyc/renpy/display/behavior.py
new file mode 100644
index 0000000..02eccf2
--- /dev/null
+++ b/unrpyc/renpy/display/behavior.py
@@ -0,0 +1,1531 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This contains various Displayables that handle events.
+
+
+import renpy.display
+import renpy.audio
+
+from renpy.display.render import render, Render
+
+import pygame
+
+def compile_event(key, keydown):
+    """
+    Compiles a keymap entry into a python expression.
+
+    keydown determines if we are dealing with keys going down (press),
+    or keys going up (release).
+    """
+    
+    # Lists or tuples get turned into or expressions.
+    if isinstance(key, (list, tuple)):
+        if not key:
+            return "(False)"
+
+        return "(" + " or ".join([compile_event(i, keydown) for i in key]) + ")"
+
+    # If it's in config.keymap, compile what's in config.keymap.
+    if key in renpy.config.keymap:
+        return compile_event(renpy.config.keymap[key], keydown)
+
+    if key is None:
+        return "(False)"
+    
+    part = key.split("_")
+
+    # Deal with the mouse.
+    if part[0] == "mousedown":
+        if keydown:
+            return "(ev.type == %d and ev.button == %d)" % (pygame.MOUSEBUTTONDOWN, int(part[1]))
+        else:
+            return "(False)"
+
+    if part[0] == "mouseup":
+        if keydown:
+            return "(ev.type == %d and ev.button == %d)" % (pygame.MOUSEBUTTONUP, int(part[1]))
+        else:
+            return "(False)"
+
+    # Deal with the Joystick.
+    if part[0] == "joy":
+        if keydown:
+            return "(ev.type == %d and ev.press and ev.press == renpy.game.preferences.joymap.get(%r, None))" % (renpy.display.core.JOYEVENT, key)
+        else:
+            return "(ev.type == %d and ev.release and ev.release == renpy.game.preferences.joymap.get(%r, None))" % (renpy.display.core.JOYEVENT, key)
+
+    # Otherwise, deal with it as a key.
+    if keydown:
+        rv = "(ev.type == %d" % pygame.KEYDOWN
+    else:
+        rv = "(ev.type == %d" % pygame.KEYUP
+
+    if part[0] == "alt":
+        part.pop(0)
+        rv += " and (ev.mod & %d)" % pygame.KMOD_ALT
+    else:
+        rv += " and not (ev.mod & %d)" % pygame.KMOD_ALT
+
+    if part[0] == "meta":
+        part.pop(0)
+        rv += " and (ev.mod & %d)" % pygame.KMOD_META
+    else:
+        rv += " and not (ev.mod & %d)" % pygame.KMOD_META
+
+    if part[0] == "shift":
+        part.pop(0)
+        rv += " and (ev.mod & %d)" % pygame.KMOD_SHIFT
+
+    if part[0] == "noshift":
+        part.pop(0)
+        rv += " and not (ev.mod & %d)" % pygame.KMOD_SHIFT
+
+    if len(part) == 1:
+        if len(part[0]) != 1:            
+            if renpy.config.developer:
+                raise Exception("Invalid key specifier %s" % key)
+            else:
+                return "(False)"
+
+        rv += " and ev.unicode == %r)" % part[0]        
+
+    else:
+        if part[0] != "K":
+            if renpy.config.developer:
+                raise Exception("Invalid key specifier %s" % key)
+            else:
+                return "(False)"
+
+        key = "_".join(part)
+        
+        rv += " and ev.key == %d)" % (getattr(pygame.constants, key)) 
+
+    return rv
+
+# These store a lambda for each compiled key in the system.
+event_cache = { }
+keyup_cache = { }
+
+def map_event(ev, name):
+    """Returns true if the event matches the named keycode being pressed."""
+
+    check_code = event_cache.get(name, None)
+    if check_code is None:
+        check_code = eval("lambda ev : " + compile_event(name, True), globals())
+        event_cache[name] = check_code
+
+    return check_code(ev)
+        
+def map_keyup(ev, name):
+    """Returns true if the event matches the named keycode being released."""
+    
+    check_code = keyup_cache.get(name, None)
+    if check_code is None:
+        check_code = eval("lambda ev : " + compile_event(name, False), globals())
+        keyup_cache[name] = check_code
+
+    return check_code(ev)
+    
+
+def skipping(ev):
+    """
+    This handles setting skipping in response to the press of one of the
+    CONTROL keys. The library handles skipping in response to TAB.
+    """
+
+    if not renpy.config.allow_skipping:
+        return
+    
+    if map_event(ev, "skip"):
+        renpy.config.skipping = "slow"
+        renpy.exports.restart_interaction()
+        
+    if map_keyup(ev, "skip"):
+        renpy.config.skipping = None 
+        renpy.exports.restart_interaction()
+
+    return
+
+
+def inspector(ev):
+    return map_event(ev, "inspector")
+
+
+##############################################################################
+# Utility functions for dealing with actions.
+    
+def predict_action(var):
+    """
+    Predicts some of the actions that may be caused by a variable.
+    """
+
+    if var is None:
+        return
+
+    if isinstance(var, renpy.ui.Action):
+        var.predict()
+    
+    if isinstance(var, (list, tuple)):
+        for i in var:
+            predict_action(i)
+    
+def run(var, *args, **kwargs):
+    """
+    Runs a variable. This is done by calling all the functions, and
+    iterating over the lists and tuples.
+    """
+    
+    if var is None:
+        return None
+
+    if isinstance(var, (list, tuple)):
+        rv = None
+
+        for i in var:
+            new_rv = run(i, *args, **kwargs)
+
+            if new_rv is not None:
+                rv = new_rv
+
+        return rv
+
+    return var(*args, **kwargs)
+
+def run_unhovered(var):
+    """
+    Calls the unhovered method on the variable, if it exists.
+    """
+    
+    if var is None:
+        return None
+
+    if isinstance(var, (list, tuple)):
+        for i in var:
+            run_unhovered(i)
+
+        return
+
+    f = getattr(var, "unhovered", None)
+    if f is not None:
+        f()
+
+def run_periodic(var, st):
+
+    if isinstance(var, (list, tuple)):
+        rv = None
+
+        for i in var:
+            v = run_periodic(i, st)
+
+            if rv is None or v < rv:
+                rv = v
+
+        return rv
+            
+    if isinstance(var, renpy.ui.Action):
+        return var.periodic(st)
+
+    
+def is_selected(clicked):
+
+    if isinstance(clicked, (list, tuple)):
+        return any(is_selected(i) for i in clicked)
+
+    elif isinstance(clicked, renpy.ui.Action):
+        return clicked.get_selected()
+    else:
+        return False
+
+    
+def is_sensitive(clicked):
+
+    if isinstance(clicked, (list, tuple)):
+        return all(is_sensitive(i) for i in clicked)
+
+    elif isinstance(clicked, renpy.ui.Action):
+        return clicked.get_sensitive()
+    else:
+        return True
+
+
+##############################################################################
+# Special-Purpose Displayables
+
+class Keymap(renpy.display.layout.Null):
+    """
+    This is a behavior that maps keys to actions that are called when
+    the key is pressed. The keys are specified by giving the appropriate
+    k_constant from pygame.constants, or the unicode for the key.
+    """
+
+    def __init__(self, replaces=None, **keymap):
+        super(Keymap, self).__init__(style='default')
+        self.keymap = keymap
+
+    def event(self, ev, x, y, st):
+
+        for name, action in self.keymap.items():
+            if map_event(ev, name):
+
+                rv = run(action)
+                
+                if rv is not None:
+                    return rv
+                
+                raise renpy.display.core.IgnoreEvent()
+
+    def predict_one_action(self):
+        for i in self.keymap.values():
+            predict_action(i)
+            
+
+class RollForward(renpy.display.layout.Null):
+    """
+    This behavior implements rollforward.
+    """
+
+    def __init__(self, value, **properties):
+        super(RollForward, self).__init__(**properties)
+        self.value = value
+
+        
+    def event(self, ev, x, y, st):
+        
+        if map_event(ev, "rollforward"):
+            renpy.game.interface.suppress_transition = True
+            renpy.game.after_rollback = True
+            renpy.game.log.rolled_forward = True
+            return self.value
+
+
+class PauseBehavior(renpy.display.layout.Null):
+    """
+    This is a class implementing the Pause behavior, which is to
+    return a value after a certain amount of time has elapsed.
+    """
+
+    def __init__(self, delay, result=False, **properties):
+        super(PauseBehavior, self).__init__(**properties)
+
+        self.delay = delay
+        self.result = result 
+
+    def event(self, ev, x, y, st):
+
+        if st >= self.delay:
+
+            # If we have been drawn since the timeout, simply return
+            # true. Otherwise, force a redraw, and return true when
+            # it comes back.
+            if renpy.game.interface.drawn_since(st - self.delay):
+                return self.result
+            else:
+                renpy.game.interface.force_redraw = True
+
+
+        renpy.game.interface.timeout(max(self.delay - st, 0))
+
+class SoundStopBehavior(renpy.display.layout.Null):
+    """
+    This is a class implementing the sound stop behavior,
+    which is to return False when a sound is no longer playing
+    on the named channel.
+    """
+
+    def __init__(self, channel, result=False, **properties):
+        super(SoundStopBehavior, self).__init__(**properties)
+
+        self.channel = channel
+        self.result = result 
+
+
+    def event(self, ev, x, y, st):
+        
+        if not renpy.audio.music.get_playing(self.channel):
+            return self.result
+
+        renpy.game.interface.timeout(.025)
+
+
+class SayBehavior(renpy.display.layout.Null):
+    """
+    This is a class that implements the say behavior,
+    which is to return True (ending the interaction) if
+    the user presses space or enter, or clicks the left
+    mouse button.
+    """
+
+    focusable = True
+
+    def __init__(self, default=True, afm=None, dismiss=[ 'dismiss' ], allow_dismiss=None, **properties):
+        super(SayBehavior, self).__init__(default=default, **properties)
+
+        if not isinstance(dismiss, (list, tuple)):
+            dismiss = [ dismiss ]
+
+        if afm is not None:
+            self.afm_length = len(afm)
+        else:
+            self.afm_length = None
+
+        # What keybindings lead to dismissal?
+        self.dismiss = dismiss
+
+        self.allow_dismiss = allow_dismiss
+        
+    def set_afm_length(self, afm_length):
+        self.afm_length = max(afm_length, 1)
+              
+    def event(self, ev, x, y, st):
+
+        if self.afm_length and renpy.game.preferences.afm_time and renpy.game.preferences.afm_enable:
+                                                          
+            afm_delay = ( 1.0 * ( renpy.config.afm_bonus + self.afm_length ) / renpy.config.afm_characters ) * renpy.game.preferences.afm_time
+
+            if renpy.game.preferences.text_cps:
+                afm_delay += 1.0 / renpy.game.preferences.text_cps * self.afm_length
+
+            if st > afm_delay:
+                if renpy.config.afm_callback:
+                    if renpy.config.afm_callback():
+                        return True
+                    else:
+                        renpy.game.interface.timeout(0.1)
+                else:
+                    return True
+            else:
+                renpy.game.interface.timeout(afm_delay - st)
+
+        for dismiss in self.dismiss:
+            
+            if map_event(ev, dismiss) and self.is_focused():
+
+                if renpy.config.skipping:
+                    renpy.config.skipping = None
+                    renpy.exports.restart_interaction()
+                    raise renpy.display.core.IgnoreEvent()
+
+                if renpy.game.preferences.using_afm_enable and renpy.game.preferences.afm_enable:
+                    renpy.game.preferences.afm_enable = False
+                    renpy.exports.restart_interaction()
+                    raise renpy.display.core.IgnoreEvent()
+                
+                if self.allow_dismiss:
+                    if not self.allow_dismiss():
+                        raise renpy.display.core.IgnoreEvent()
+
+                return True
+            
+        skip_delay = renpy.config.skip_delay / 1000.0
+
+        if renpy.config.allow_skipping and renpy.config.skipping:
+            
+            if st >= skip_delay:
+                if renpy.game.preferences.skip_unseen:
+                    return True
+                elif renpy.config.skipping == "fast":
+                    return True
+                elif renpy.game.context().seen_current(True):
+                    return True
+            else:
+                renpy.game.interface.timeout(skip_delay - st)
+
+
+        return None
+
+
+##############################################################################
+# Button
+    
+class Button(renpy.display.layout.Window):
+
+    keymap = { }
+    action = None
+    
+    def __init__(self, child=None, style='button', clicked=None,
+                 hovered=None, unhovered=None, action=None, role=None,
+                 time_policy=None, keymap={},
+                 **properties):
+
+        super(Button, self).__init__(child, style=style, **properties)
+
+        if isinstance(clicked, renpy.ui.Action):
+            action = clicked
+        
+        if action is not None:
+            clicked = action
+
+            if not is_sensitive(action):
+                clicked = None
+            
+        if role is None:
+            if action:
+                if is_selected(action):
+                    role = 'selected_'
+                else:
+                    role = ''
+            else:
+                role = ''
+
+        self.action = action
+        self.activated = False
+        self.clicked = clicked
+        self.hovered = hovered
+        self.unhovered = unhovered
+        self.focusable = clicked is not None
+        self.role = role
+        self.keymap = keymap
+        
+        self.time_policy_data = None
+
+    def predict_one_action(self):
+        predict_action(self.clicked)
+        predict_action(self.hovered)
+        predict_action(self.unhovered)
+
+        if self.keymap:
+            for v in self.keymap.values():
+                predict_action(v)
+        
+    def render(self, width, height, st, at):
+
+        if self.style.time_policy:
+            st, self.time_policy_data = self.style.time_policy(st, self.time_policy_data, self.style)
+        
+        rv = super(Button, self).render(width, height, st, at)
+
+        if self.clicked:
+
+            rect = self.style.focus_rect
+            if rect is not None:
+                fx, fy, fw, fh = rect
+            else:
+                fx = self.style.left_margin
+                fy = self.style.top_margin
+                fw = rv.width - self.style.right_margin
+                fh = rv.height - self.style.bottom_margin
+
+            mask = self.style.focus_mask
+
+            if mask is True:
+                mask = rv
+            elif mask is not None:
+                mask = renpy.easy.displayable(mask)
+                mask = renpy.display.render.render(mask, rv.width, rv.height, st, at)
+
+            if mask is not None:
+                fmx = 0
+                fmy = 0
+            else:
+                fmx = None
+                fmy = None
+                
+            rv.add_focus(self, None,
+                         fx, fy, fw, fh,
+                         fmx, fmy, mask)
+            
+        return rv
+
+
+    def focus(self, default=False):
+        super(Button, self).focus(default)
+
+        if self.activated:
+            return None
+
+        rv = None
+
+        if not default:
+            rv = run(self.hovered)
+
+        self.set_transform_event(self.role + "hover")
+        self.child.set_transform_event(self.role + "hover")
+        
+        return rv
+        
+
+    def unfocus(self, default=False):
+        super(Button, self).unfocus(default)
+
+        if self.activated:
+            return None
+
+        if not default:
+            run_unhovered(self.hovered)
+            run(self.unhovered)
+
+        self.set_transform_event(self.role + "idle")
+        self.child.set_transform_event(self.role + "idle")
+        
+            
+    def per_interact(self):
+        if not self.clicked:
+            self.set_style_prefix(self.role + "insensitive_", True)
+        else:
+            self.set_style_prefix(self.role + "idle_", True)
+
+        super(Button, self).per_interact()
+            
+    def event(self, ev, x, y, st):
+
+        # Call self.action.periodic()
+        timeout = run_periodic(self.action, st)
+
+        if timeout is not None:
+            renpy.game.interface.timeout(timeout)
+        
+        # If we have a child, try passing the event to it. (For keyboard
+        # events, this only happens if we're focused.)
+        if self.is_focused() or not (ev.type == pygame.KEYDOWN or ev.type == pygame.KEYUP):
+            rv = super(Button, self).event(ev, x, y, st)
+            if rv is not None:
+                return rv
+        
+        # If not focused, ignore all events.
+        if not self.is_focused():
+            return None
+
+        # Check the keymap.
+        for name, action in self.keymap.items():
+            if map_event(ev, name):
+                return run(action)
+                
+        # Ignore as appropriate:
+        if map_event(ev, "button_ignore") and self.clicked:
+            raise renpy.display.core.IgnoreEvent()
+            
+        # If clicked, 
+        if map_event(ev, "button_select") and self.clicked:
+
+            self.activated = True
+            self.style.set_prefix(self.role + 'activate_')
+
+            if self.style.sound:
+                renpy.audio.music.play(self.style.sound, channel="sound")
+                    
+            rv = run(self.clicked)
+
+            if rv is not None:
+                return rv
+            else:
+                self.activated = False
+
+                if self.is_focused():
+                    self.set_style_prefix(self.role + "hover_", True)
+                else:
+                    self.set_style_prefix(self.role + "idle_", True)
+                    
+                raise renpy.display.core.IgnoreEvent()
+                    
+        return None
+
+
+    def set_style_prefix(self, prefix, root):
+        if root:
+            super(Button, self).set_style_prefix(prefix, root)
+    
+
+# Reimplementation of the TextButton widget as a Button and a Text
+# widget.
+def TextButton(text, style='button', text_style='button_text',
+               clicked=None, **properties):
+
+    text = renpy.text.text.Text(text, style=text_style) #@UndefinedVariable
+    return Button(text, style=style, clicked=clicked, **properties)
+
+class ImageButton(Button):
+    """
+    Used to implement the guts of an image button.
+    """
+
+    def __init__(self,
+                 idle_image,
+                 hover_image,
+                 insensitive_image = None,
+                 activate_image = None,
+                 selected_idle_image = None,
+                 selected_hover_image = None,
+                 selected_insensitive_image = None,
+                 selected_activate_image = None,                 
+                 style='image_button',
+                 clicked=None,
+                 hovered=None,
+                 **properties):
+
+        insensitive_image = insensitive_image or idle_image
+        activate_image = activate_image or hover_image
+
+        selected_idle_image = selected_idle_image or idle_image
+        selected_hover_image = selected_hover_image or hover_image
+        selected_insensitive_image = selected_insensitive_image or insensitive_image
+        selected_activate_image = selected_activate_image or activate_image
+
+        self.state_children = dict(
+            idle_ = renpy.easy.displayable(idle_image),
+            hover_ = renpy.easy.displayable(hover_image),
+            insensitive_ = renpy.easy.displayable(insensitive_image),
+            activate_ = renpy.easy.displayable(activate_image),
+
+            selected_idle_ = renpy.easy.displayable(selected_idle_image),
+            selected_hover_ = renpy.easy.displayable(selected_hover_image),
+            selected_insensitive_ = renpy.easy.displayable(selected_insensitive_image),
+            selected_activate_ = renpy.easy.displayable(selected_activate_image),
+            )
+
+        super(ImageButton, self).__init__(renpy.display.layout.Null(),
+                                          style=style,
+                                          clicked=clicked,
+                                          hovered=hovered,
+                                          **properties)
+        
+    def visit(self):
+        return list(self.state_children.values())
+
+    def get_child(self):
+        return self.style.child or self.state_children[self.style.prefix]
+
+
+# This is used for an input that takes its focus from a button. 
+class HoveredProxy(object):
+    def __init__(self, a, b):
+        self.a = a
+        self.b = b
+
+    def __call__(self):
+        self.a()
+        if self.b:
+            return self.b()
+    
+                
+class Input(renpy.text.text.Text): #@UndefinedVariable
+    """
+    This is a Displayable that takes text as input.
+    """
+
+    changed = None
+    prefix = ""
+    suffix = ""
+    caret_pos = 0
+    
+    def __init__(self,
+                 default="",
+                 length=None,
+                 style='input',
+                 allow=None,
+                 exclude=None,
+                 prefix="",
+                 suffix="",
+                 changed=None,
+                 button=None,
+                 replaces=None,
+                 editable=True,
+                 **properties):
+
+        super(Input, self).__init__("", style=style, replaces=replaces, substitute=False, **properties)
+
+        self.content = str(default)
+        self.length = length
+
+        self.allow = allow
+        self.exclude = exclude
+        self.prefix = prefix
+        self.suffix = suffix
+
+        self.changed = changed
+
+        self.editable = editable
+
+        caretprops = { 'color' : None }
+        
+        for i in properties:
+            if i.endswith("color"):
+                caretprops[i] = properties[i]
+
+        self.caret = renpy.display.image.Solid(xmaximum=1, style=style, **caretprops)
+        self.caret_pos = len(self.content)
+
+        if button:
+            self.editable = False
+            button.hovered = HoveredProxy(self.enable, button.hovered)
+            button.unhovered = HoveredProxy(self.disable, button.unhovered)
+
+        if isinstance(replaces, Input):
+            self.content = replaces.content
+            self.editable = replaces.editable
+            self.caret_pos = replaces.caret_pos
+
+        self.update_text(self.content, self.editable)
+
+
+    def update_text(self, content, editable):
+
+        if content != self.content or editable != self.editable:
+            renpy.display.render.redraw(self, 0)
+                                            
+        if content != self.content:
+            self.content = content
+
+            if self.changed:
+                self.changed(content)
+                
+        if content == "":
+            content = "\u200b"
+                
+        self.editable = editable
+
+        # Choose the caret.
+        caret = self.style.caret
+        if caret is None:
+            caret = self.caret
+                                            
+        if editable:
+            l = len(content)
+            self.set_text([self.prefix, content[0:self.caret_pos].replace("{", "{{"), caret,
+                                        content[self.caret_pos:l].replace("{", "{{"), self.suffix])
+        else:
+            self.set_text([self.prefix, content.replace("{", "{{"), self.suffix ])
+
+    # This is needed to ensure the caret updates properly.
+    def set_style_prefix(self, prefix, root):
+        if prefix != self.style.prefix:
+            self.update_text(self.content, self.editable)
+
+        super(Input, self).set_style_prefix(prefix, root)
+
+    def enable(self):
+        self.update_text(self.content, True)
+
+    def disable(self):
+        self.update_text(self.content, False)
+            
+    def event(self, ev, x, y, st):
+
+        if not self.editable:
+            return None
+        
+        l = len(self.content)
+        
+        if map_event(ev, "input_backspace"):
+
+            if self.content and self.caret_pos > 0:
+                content = self.content[0:self.caret_pos-1] + self.content[self.caret_pos:l]
+                self.caret_pos -= 1
+                self.update_text(content, self.editable)
+                                            
+            renpy.display.render.redraw(self, 0)
+            raise renpy.display.core.IgnoreEvent()
+
+        elif map_event(ev, "input_enter"):
+            if not self.changed:
+                return self.content
+
+        elif map_event(ev, "input_left"):
+            if self.caret_pos > 0:
+                self.caret_pos -= 1
+                self.update_text(self.content, self.editable)
+                                            
+            renpy.display.render.redraw(self, 0)
+            raise renpy.display.core.IgnoreEvent()
+
+        elif map_event(ev, "input_right"):
+            if self.caret_pos < l:
+                self.caret_pos += 1
+                self.update_text(self.content, self.editable)
+                                            
+            renpy.display.render.redraw(self, 0)
+            raise renpy.display.core.IgnoreEvent()
+
+        elif map_event(ev, "input_delete"):
+            if self.caret_pos < l:
+                content = self.content[0:self.caret_pos] + self.content[self.caret_pos+1:l]
+                self.update_text(content, self.editable)
+                                            
+            renpy.display.render.redraw(self, 0)
+            raise renpy.display.core.IgnoreEvent()
+
+        elif ev.type == pygame.KEYDOWN and ev.str:
+            if ord(ev.str[0]) < 32:
+                return None
+                
+            if self.length and len(self.content) >= self.length:
+                raise renpy.display.core.IgnoreEvent()
+
+            if self.allow and ev.str not in self.allow:
+                raise renpy.display.core.IgnoreEvent()
+
+            if self.exclude and ev.str in self.exclude:
+                raise renpy.display.core.IgnoreEvent()
+
+            content = self.content[0:self.caret_pos] + ev.str + self.content[self.caret_pos:l]
+            self.caret_pos += 1
+
+            self.update_text(content, self.editable)
+
+            raise renpy.display.core.IgnoreEvent()
+
+# A map from adjustment to lists of displayables that want to be redrawn
+# if the adjustment changes.
+adj_registered = { }
+
+# This class contains information about an adjustment that can change the
+# position of content.
+class Adjustment(renpy.object.Object):
+    """
+    :doc: ui
+    :name: ui.adjustment class
+
+    Adjustment objects represent a value that can be adjusted by a bar
+    or viewport. They contain information about the value, the range
+    of the value, and how to adjust the value in small steps and large
+    pages.
+
+    
+    """
+    
+    def __init__(self, range=1, value=0, step=None, page=0, changed=None, adjustable=None, ranged=None): #@ReservedAssignment
+        """
+        The following parameters correspond to fields or properties on
+        the adjustment object:
+
+        `range`
+            The range of the adjustment, a number.
+
+        `value`
+            The value of the adjustment, a number.
+            
+        `step`
+            The step size of the adjustment, a number. If None, then
+            defaults to 1/10th of a page, if set. Otherwise, defaults
+            to the 1/20th of the range.
+
+            This is used when scrolling a viewport with the mouse wheel.
+            
+        `page`
+            The page size of the adjustment. If None, this is set
+            automatically by a viewport. If never set, defaults to 1/10th
+            of the range.
+
+            It's can be used when clicking on a scrollbar.
+
+        The following parameters control the behavior of the adjustment.
+
+        `adjustable`
+             If True, this adjustment can be changed by a bar. If False,
+             it can't.
+
+             It defaults to being adjustable if a `changed` function
+             is given or if the adjustment is associated with a viewport,
+             and not adjustable otherwise.
+
+        `changed`
+            This function is called with the new value when the value of
+            the adjustment changes.
+
+        `ranged`
+            This function is called with the adjustment object when
+            the range of the adjustment is set by a viewport.
+
+        .. method:: change(value)
+
+            Changes the value of the adjustment to `value`, updating
+            any bars and viewports that use the adjustment.
+         """
+
+
+        super(Adjustment, self).__init__()
+
+        if adjustable is None:
+            if changed:
+                adjustable = True
+        
+        self._value = value
+        self._range = range
+        self._page = page
+        self._step = step
+        self.changed = changed
+        self.adjustable = adjustable
+        self.ranged = ranged
+        
+    def get_value(self):
+        if self._value > self._range:
+            return self._range
+        
+        return self._value
+
+    def set_value(self, v):
+        self._value = v
+
+    value = property(get_value, set_value)
+        
+    def get_range(self):
+        return self._range
+
+    def set_range(self, v):
+        self._range = v
+        if self.ranged:
+            self.ranged(self)
+        
+    range = property(get_range, set_range) #@ReservedAssignment
+
+    def get_page(self):
+        if self._page is not None:
+            return self._page
+
+        return self._range / 10
+
+    def set_page(self, v):
+        self._page = v
+
+    page = property(get_page, set_page)
+
+    def get_step(self):
+        if self._step is not None:
+            return self._step
+
+        if self._page is not None and self.page > 0:
+            return self._page / 10
+
+        if isinstance(self._range, float):
+            return self._range / 10
+        else:
+            return 1
+
+    def set_step(self, v):
+        self._step = v
+
+    step = property(get_step, set_step)
+        
+    # Register a displayable to be redrawn when this adjustment changes.
+    def register(self, d):
+        adj_registered.setdefault(self, [ ]).append(d)
+            
+    def change(self, value):
+
+        if value < 0:
+            value = 0
+        if value > self._range:
+            value = self._range
+
+        if value != self._value:
+            self._value = value
+            for d in adj_registered.setdefault(self, [ ]):
+                renpy.display.render.redraw(d, 0)
+            if self.changed:
+                return self.changed(value)
+
+        return None
+            
+class Bar(renpy.display.core.Displayable):
+    """
+    Implements a bar that can display an integer value, and respond
+    to clicks on that value.
+    """
+
+    __version__ = 2
+    
+    def after_upgrade(self, version):
+
+        if version < 1:
+            self.adjustment = Adjustment(self.range, self.value, changed=self.changed) # E1101
+            self.adjustment.register(self)
+            del self.range # E1101
+            del self.value # E1101 
+            del self.changed # E1101
+
+        if version < 2:
+            self.value = None
+            
+    def __init__(self,
+                 range=None, #@ReservedAssignment
+                 value=None,
+                 width=None,
+                 height=None,
+                 changed=None,
+                 adjustment=None,
+                 step=None,
+                 page=None,
+                 bar=None,
+                 style=None,
+                 vertical=False,
+                 replaces=None,
+                 hovered=None,
+                 unhovered=None,
+                 **properties):
+
+        self.value = None
+        
+        if adjustment is None:
+            if isinstance(value, renpy.ui.BarValue):
+
+                if isinstance(replaces, Bar):
+                    value.replaces(replaces.value)
+
+                self.value = value
+                adjustment = value.get_adjustment()
+                renpy.game.interface.timeout(0)
+            else:                 
+                adjustment = Adjustment(range, value, step=step, page=page, changed=changed)
+
+        if style is None:
+            if self.value is not None:
+                if vertical:
+                    style = self.value.get_style()[1]
+                else:
+                    style = self.value.get_style()[0]                
+            else:
+                if vertical:
+                    style = 'vbar'
+                else:
+                    style = 'bar'
+                
+        if width is not None:
+            properties['xmaximum'] = width
+
+        if height is not None:
+            properties['ymaximum'] = height
+
+        super(Bar, self).__init__(style=style, **properties)
+
+        self.adjustment = adjustment
+        self.focusable = True
+        
+        # These are set when we are first rendered.
+        self.thumb_dim = 0
+        self.height = 0
+        self.width = 0
+        self.hidden = False
+
+        self.hovered = hovered
+        self.unhovered = unhovered        
+
+    def per_interact(self):
+        self.focusable = self.adjustment.adjustable
+        self.adjustment.register(self)
+
+    def predict_one(self):
+        pd = renpy.display.predict.displayable
+        style = self.style
+        
+        pd(style.insensitive_fore_bar)
+        pd(style.idle_fore_bar)
+        pd(style.hover_fore_bar)
+        pd(style.selected_idle_fore_bar)
+        pd(style.selected_hover_fore_bar)
+
+        pd(style.insensitive_aft_bar)
+        pd(style.idle_aft_bar)
+        pd(style.hover_aft_bar)
+        pd(style.selected_idle_aft_bar)
+        pd(style.selected_hover_aft_bar)
+        
+        pd(style.insensitive_thumb)
+        pd(style.idle_thumb)
+        pd(style.hover_thumb)
+        pd(style.selected_idle_thumb)
+        pd(style.selected_hover_thumb)
+
+        pd(style.insensitive_thumb_shadow)
+        pd(style.idle_thumb_shadow)
+        pd(style.hover_thumb_shadow)
+        pd(style.selected_idle_thumb_shadow)
+        pd(style.selected_hover_thumb_shadow)
+        
+    def render(self, width, height, st, at):
+
+        # Handle redrawing.
+        if self.value is not None:
+            redraw = self.value.periodic(st)
+
+            if redraw is not None:
+                renpy.display.render.redraw(self, redraw)
+        
+        # Store the width and height for the event function to use.
+        self.width = width
+        self.height = height
+        range = self.adjustment.range #@ReservedAssignment
+        value = self.adjustment.value
+        page = self.adjustment.page
+
+        if range <= 0:
+            if self.style.unscrollable == "hide":
+                self.hidden = True
+                return renpy.display.render.Render(width, height)
+            elif self.style.unscrollable == "insensitive":
+                self.set_style_prefix("insensitive_", True)
+
+        self.hidden = False
+        
+        if self.style.bar_invert ^ self.style.bar_vertical:
+            value = range - value
+
+        bar_vertical = self.style.bar_vertical
+
+        if bar_vertical:
+            dimension = height
+        else:
+            dimension = width
+
+        fore_gutter = self.style.fore_gutter
+        aft_gutter = self.style.aft_gutter
+
+        active = dimension - fore_gutter - aft_gutter
+        if range:
+            thumb_dim = active * page / (range + page) 
+        else:
+            thumb_dim = active
+
+        thumb_offset = abs(self.style.thumb_offset)
+
+        if bar_vertical:
+            thumb = render(self.style.thumb, width, thumb_dim, st, at)
+            thumb_shadow = render(self.style.thumb_shadow, width, thumb_dim, st, at)
+            thumb_dim = thumb.height
+        else:
+            thumb = render(self.style.thumb, thumb_dim, height, st, at)
+            thumb_shadow = render(self.style.thumb_shadow, thumb_dim, height, st, at)
+            thumb_dim = thumb.width
+
+        # Remove the offset from the thumb.
+        thumb_dim -= thumb_offset * 2
+        self.thumb_dim = thumb_dim
+        
+        active -= thumb_dim
+
+        if range:
+            fore_size = active * value / range
+        else:
+            fore_size = active
+
+        fore_size = int(fore_size)
+            
+        aft_size = active - fore_size
+
+        fore_size += fore_gutter
+        aft_size += aft_gutter
+
+        rv = renpy.display.render.Render(width, height)
+        
+        if bar_vertical:
+
+            if self.style.bar_resizing:
+                foresurf = render(self.style.fore_bar, width, fore_size, st, at)
+                aftsurf = render(self.style.aft_bar, width, aft_size, st, at)
+                rv.blit(thumb_shadow, (0, fore_size - thumb_offset))
+                rv.blit(foresurf, (0, 0), main=False)
+                rv.blit(aftsurf, (0, height-aft_size), main=False)
+                rv.blit(thumb, (0, fore_size - thumb_offset))
+
+            else:
+                foresurf = render(self.style.fore_bar, width, height, st, at)
+                aftsurf = render(self.style.aft_bar, width, height, st, at)
+
+                rv.blit(thumb_shadow, (0, fore_size - thumb_offset))
+                rv.blit(foresurf.subsurface((0, 0, width, fore_size)), (0, 0), main=False)
+                rv.blit(aftsurf.subsurface((0, height - aft_size, width, aft_size)), (0, height - aft_size), main=False)
+                rv.blit(thumb, (0, fore_size - thumb_offset))
+
+        else:
+            if self.style.bar_resizing:
+                foresurf = render(self.style.fore_bar, fore_size, height, st, at)
+                aftsurf = render(self.style.aft_bar, aft_size, height, st, at)
+                rv.blit(thumb_shadow, (fore_size - thumb_offset, 0))
+                rv.blit(foresurf, (0, 0), main=False)
+                rv.blit(aftsurf, (width-aft_size, 0), main=False)
+                rv.blit(thumb, (fore_size - thumb_offset, 0))
+
+            else:
+                foresurf = render(self.style.fore_bar, width, height, st, at)
+                aftsurf = render(self.style.aft_bar, width, height, st, at)
+
+                rv.blit(thumb_shadow, (fore_size - thumb_offset, 0))
+                rv.blit(foresurf.subsurface((0, 0, fore_size, height)), (0, 0), main=False)
+                rv.blit(aftsurf.subsurface((width - aft_size, 0, aft_size, height)), (width-aft_size, 0), main=False)
+                rv.blit(thumb, (fore_size - thumb_offset, 0))
+        
+        if self.focusable:
+            rv.add_focus(self, None, 0, 0, width, height)
+
+        return rv
+
+    
+    def focus(self, default=False):
+        super(Bar, self).focus(default)
+        self.set_transform_event("hover")
+        
+        if not default:
+            run(self.hovered)
+        
+        
+    def unfocus(self, default=False):
+        super(Bar, self).unfocus()
+        self.set_transform_event("idle")
+
+        if not default:
+            run_unhovered(self.hovered)
+            run(self.unhovered)
+    
+    def event(self, ev, x, y, st):
+
+        if not self.focusable:
+            return None
+
+        if not self.is_focused():
+            return None
+
+        if self.hidden:
+            return None
+        
+        range = self.adjustment.range #@ReservedAssignment
+        old_value = self.adjustment.value
+        value = old_value
+
+        vertical = self.style.bar_vertical
+        invert = self.style.bar_invert ^ vertical
+        if invert:
+            value = range - value
+        
+        grabbed = (renpy.display.focus.get_grab() is self)
+        just_grabbed = False
+            
+        if not grabbed and map_event(ev, "bar_activate"):
+            renpy.display.focus.set_grab(self)
+            just_grabbed = True
+            grabbed = True
+
+        if grabbed:
+
+            if vertical:
+                increase = "bar_down"
+                decrease = "bar_up"
+            else:
+                increase = "bar_right"
+                decrease = "bar_left"
+                
+            if map_event(ev, decrease):
+                value -= self.adjustment.step
+
+            if map_event(ev, increase):
+                value += self.adjustment.step
+
+            if ev.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+
+                if vertical:
+
+                    tgutter = self.style.fore_gutter
+                    bgutter = self.style.aft_gutter
+                    zone_height = self.height - tgutter - bgutter - self.thumb_dim
+                    if zone_height:
+                        value = (y - tgutter - self.thumb_dim / 2) * range / zone_height
+                    else:
+                        value = 0
+                        
+                else:
+                    lgutter = self.style.fore_gutter
+                    rgutter = self.style.aft_gutter
+                    zone_width = self.width - lgutter - rgutter - self.thumb_dim   
+                    if zone_width:
+                        value = (x - lgutter - self.thumb_dim / 2) * range / zone_width
+                    else:
+                        value = 0
+                        
+            if isinstance(range, int):
+                value = int(value)
+
+            if value < 0:
+                value = 0
+
+            if value > range:
+                value = range
+
+        if invert:
+            value = range - value
+            
+        if grabbed and not just_grabbed and map_event(ev, "bar_deactivate"):
+            renpy.display.focus.set_grab(None)
+
+        if value != old_value:
+            return self.adjustment.change(value)
+
+        return None
+
+
+class Conditional(renpy.display.layout.Container):
+    """
+    This class renders its child if and only if the condition is
+    true. Otherwise, it renders nothing. (Well, a Null). 
+
+    Warning: the condition MUST NOT update the game state in any
+    way, as that would break rollback.
+    """
+
+    def __init__(self, condition, *args, **properties):
+        super(Conditional, self).__init__(*args, **properties)
+
+        self.condition = condition
+        self.null = renpy.display.layout.Null()
+
+        self.state = eval(self.condition, vars(renpy.store))
+
+    def render(self, width, height, st, at):
+        if self.state:
+            return render(self.child, width, height, st, at)
+        else:
+            return render(self.null, width, height, st, at)
+
+    def event(self, ev, x, y, st):
+
+        state = eval(self.condition, vars(renpy.store))
+
+        if state != self.state:
+            renpy.display.render.redraw(self, 0)
+
+        self.state = state
+
+        if state:
+            return self.child.event(ev, x, y, st)
+        
+            
+class TimerState(renpy.python.RevertableObject):
+    """
+    Stores the state of the timer, which may need to be rolled back.
+    """
+    
+    # Prevents us from having to worry about our initialization being
+    # rolled back.
+    started = False
+    next_event = None
+            
+class Timer(renpy.display.layout.Null):
+
+    __version__ = 1
+
+    started = False
+    
+    def after_upgrade(self, version):
+        if version < 1:        
+            self.state = TimerState()
+            self.state.started = self.started
+            self.state.next_event = self.next_event
+    
+    def __init__(self, delay, action=None, repeat=False, args=(), kwargs={}, replaces=None, **properties):
+        super(Timer, self).__init__(**properties)
+
+        if action is None:
+            raise Exception("A timer must have an action supplied.")
+        
+        if delay <= 0:
+            raise Exception("A timer's delay must be > 0.")
+
+        # The delay.
+        self.delay = delay
+
+        # Should we repeat the event?
+        self.repeat = repeat
+
+        # The time the next event should occur.
+        self.next_event = None
+
+        # The function and its arguments.
+        self.function = action
+        self.args = args
+        self.kwargs = kwargs
+
+        # Did we start the timer?
+        self.started = False
+
+        if replaces is not None:
+            self.state = replaces.state
+        else:
+            self.state = TimerState()
+
+    
+    def event(self, ev, x, y, st):
+
+        state = self.state
+
+        if not state.started:
+            state.started = True
+            state.next_event = st + self.delay
+        
+        if state.next_event is None:
+            return
+        
+        if st < state.next_event:
+            renpy.game.interface.timeout(state.next_event - st)
+            return
+
+        if not self.repeat:
+            state.next_event = None
+        else:
+            state.next_event = state.next_event + self.delay
+            if state.next_event < st:
+                state.next_event = st + self.delay
+
+            renpy.game.interface.timeout(state.next_event - st)
+
+        return run(self.function, *self.args, **self.kwargs)
+
+    
+class MouseArea(renpy.display.core.Displayable):
+
+    def __init__(self, hovered=None, unhovered=None, replaces=None, **properties):
+        super(MouseArea, self).__init__(**properties)
+
+        self.hovered = hovered
+        self.unhovered = unhovered
+        
+        # Are we hovered right now?
+        self.is_hovered = False
+
+        if replaces is not None:
+            self.is_hovered = replaces.is_hovered
+
+        # Taken from the render.
+        self.width = 0
+        self.height = 0
+            
+
+    def render(self, width, height, st, at):
+        self.width = width
+        self.height = height
+
+        return Render(width, height)
+
+    def event(self, ev, x, y, st):
+
+        if 0 <= x < self.width and 0 <= y < self.height:
+            is_hovered = True
+        else:
+            is_hovered = False
+
+        if is_hovered and not self.is_hovered:
+            self.is_hovered = True
+
+            return run(self.hovered)
+
+        elif not is_hovered and self.is_hovered:
+            self.is_hovered = False
+
+            run_unhovered(self.hovered)
+            run(self.unhovered)
+
+
diff --git a/unrpyc/renpy/display/core.py b/unrpyc/renpy/display/core.py
new file mode 100644
index 0000000..4731df0
--- /dev/null
+++ b/unrpyc/renpy/display/core.py
@@ -0,0 +1,2463 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains code for initializing and managing the display
+# window.
+
+import renpy.display
+import renpy.audio
+import renpy.text
+
+import pygame #@UnusedImport
+
+import sys
+import os
+import time
+import io
+import threading
+
+try:
+    import android #@UnresolvedImport @UnusedImport
+    import android.sound #@UnresolvedImport
+except:
+    android = None
+
+# Is the cpu idle enough to do other things?
+cpu_idle = threading.Event()
+cpu_idle.clear()
+
+# Need to be +4, so we don't interfere with FFMPEG's events.
+TIMEEVENT = pygame.USEREVENT + 4
+PERIODIC = pygame.USEREVENT + 5
+JOYEVENT = pygame.USEREVENT + 6
+REDRAW = pygame.USEREVENT + 7
+
+# All events except for TIMEEVENT and REDRAW
+ALL_EVENTS = [ i for i in range(0, REDRAW + 1) if i != TIMEEVENT and i != REDRAW ]
+
+# The number of msec between periodic events.
+PERIODIC_INTERVAL = 50
+
+# Time management.
+time_base = None
+
+def init_time():
+    global time_base
+    time_base = time.time() - pygame.time.get_ticks() / 1000.0
+
+def get_time():
+    return time_base + pygame.time.get_ticks() / 1000.0
+
+
+def displayable_by_tag(layer, tag):
+    """
+    Get the displayable on the given layer with the given tag.
+    """
+
+    return renpy.game.context().scene_lists.get_displayable_by_tag(layer, tag)
+    
+class IgnoreEvent(Exception):
+    """
+    Exception that is raised when we want to ignore an event, but
+    also don't want to return anything.
+    """
+
+    pass
+
+class EndInteraction(Exception):
+    """
+    Exception that can be raised (for example, during the render method of 
+    a displayable) to end the current interaction immediately.
+    """
+
+    def __init__(self, value):
+        self.value = value
+
+class absolute(float):
+    """
+    This represents an absolute float coordinate.
+    """
+    __slots__ = [ ]
+
+
+class Displayable(renpy.object.Object):
+    """
+    The base class for every object in Ren'Py that can be
+    displayed to the screen.
+
+    Drawables will be serialized to a savegame file. Therefore, they
+    shouldn't store non-serializable things (like pygame surfaces) in
+    their fields.
+    """
+
+    # Some invariants about method call order:
+    #
+    # per_interact is called before render.
+    # render is called before event.
+    #
+    # get_placement can be called at any time, so can't
+    # assume anything.
+    
+    activated = False
+    focusable = False
+    full_focus_name = None
+    role = ''
+
+    # The event we'll pass on to our parent transform.
+    transform_event = None
+
+    # Can we change our look in response to transform_events?
+    transform_event_responder = False
+    
+    def __init__(self, focus=None, default=False, style='default', **properties): # W0231
+        self.style = renpy.style.Style(style, properties, heavy=True)
+        self.focus_name = focus
+        self.default = default
+
+    def find_focusable(self, callback, focus_name):
+
+        focus_name = self.focus_name or focus_name
+        
+        if self.focusable:
+            callback(self, focus_name)
+
+        for i in self.visit():
+            if i is None:
+                continue
+
+            i.find_focusable(callback, focus_name)
+
+            
+    def focus(self, default=False):
+        """
+        Called to indicate that this widget has the focus.
+        """
+
+        if not self.activated:
+            self.set_style_prefix(self.role + "hover_", True)
+        
+        if not default and not self.activated:
+            if self.style.sound:
+                renpy.audio.music.play(self.style.sound, channel="sound")
+
+    def unfocus(self, default=False):
+        """
+        Called to indicate that this widget has become unfocused.
+        """
+
+        if not self.activated:
+            self.set_style_prefix(self.role + "idle_", True)
+
+    def is_focused(self):
+
+        if renpy.display.focus.grab and renpy.display.focus.grab is not self:
+            return
+
+        return renpy.game.context().scene_lists.focused is self
+
+    def set_style_prefix(self, prefix, root):
+        """
+        Called to set the style prefix of this widget and its child
+        widgets, if any.
+
+        `root` - True if this is the root of a style tree, False if this
+        has been passed on to a child.
+        """
+
+        if prefix == self.style.prefix:
+            return
+        
+        self.style.set_prefix(prefix)
+        renpy.display.render.redraw(self, 0)
+
+    def parameterize(self, name, parameters):
+        """
+        Called to parameterize this. By default, we don't take any
+        parameters.
+        """
+
+        if parameters:
+            raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" %
+                            (' '.join(name), ' '.join(parameters)))
+
+        return self
+
+    def render(self, width, height, st, at):
+        """
+        Called to display this displayable. This is called with width
+        and height parameters, which give the largest width and height
+        that this drawable can be drawn to without overflowing some
+        bounding box. It's also given two times. It returns a Surface
+        that is the current image of this drawable. 
+ 
+        @param st: The time since this widget was first shown, in seconds.
+        @param at: The time since a similarly named widget was first shown,
+        in seconds.        
+        """
+
+        assert False, "Draw not implemented."
+
+    def event(self, ev, x, y, st):
+        """
+        Called to report than an event has occured. Ev is the raw
+        pygame event object representing that event. If the event
+        involves the mouse, x and y are the translation of the event
+        into the coordinates of this displayable. st is the time this
+        widget has been shown for.
+
+        @returns A value that should be returned from Interact, or None if
+        no value is appropriate.
+        """
+
+        return None
+
+    def get_placement(self):
+        """
+        Returns a style object containing placement information for
+        this Displayable. Children are expected to overload this
+        to return something more sensible.
+        """
+
+        return self.style.get_placement()
+    
+    def visit_all(self, callback):
+        """
+        Calls the callback on this displayable and all children of this
+        displayable.
+        """
+
+        for d in self.visit():
+            if not d:
+                continue
+            d.visit_all(callback)
+
+        callback(self)
+        
+    def visit(self):
+        """
+        Called to ask the displayable to return a list of its children
+        (including children taken from styles). For convenience, this
+        list may also include None values.
+        """
+
+        return [ ]
+
+    def per_interact(self):
+        """
+        Called once per widget per interaction.
+        """
+
+        return None
+
+    def predict_one(self):
+        """
+        Called to ask this displayable to call the callback with all
+        the images it may want to load.
+        """
+
+        return
+
+    def predict_one_action(self):
+        """
+        Called to ask this displayable to cause image prediction
+        to occur for images that may be loaded by its actions.
+        """
+
+        return 
+    
+    def place(self, dest, x, y, width, height, surf, main=True):
+        """
+        This draws this Displayable onto a destination surface, using
+        the placement style information returned by this object's
+        get_placement() method.
+
+        @param dest: The surface that this displayable will be drawn
+        on.
+
+        @param x: The minimum x coordinate on this surface that this
+        Displayable will be drawn to.
+
+        @param y: The minimum y coordinate on this surface that this
+        displayable will be drawn to.
+
+        @param width: The width of the area allocated to this
+        Displayable.
+
+        @param height: The height of the area allocated to this
+        Displayable.
+
+        @param surf: The surface returned by a previous call to
+        self.render().
+        """
+        
+        xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.get_placement()
+        
+        if xpos is None:
+            xpos = 0
+        if ypos is None:
+            ypos = 0
+        if xanchor is None:
+            xanchor = 0
+        if yanchor is None:
+            yanchor = 0
+        if xoffset is None:
+            xoffset = 0
+        if yoffset is None:
+            yoffset = 0
+            
+        # We need to use type, since isinstance(absolute(0), float).
+        if xpos.__class__ is float:
+            xpos *= width
+
+        if xanchor.__class__ is float:
+            xanchor *= surf.width
+
+        xpos += x + xoffset - xanchor
+            
+        # y
+
+        if ypos.__class__ is float:
+            ypos *= height
+            
+        if yanchor.__class__ is float:
+            yanchor *= surf.height
+
+        ypos += y + yoffset - yanchor
+
+        if dest is not None:
+            if subpixel:
+                dest.subpixel_blit(surf, (xpos, ypos), main, main, None)
+            else:
+                dest.blit(surf, (xpos, ypos), main, main, None)
+            
+        return xpos, ypos
+
+    def set_transform_event(self, event):
+        """
+        Sets the transform event of this displayable to event.
+        """
+
+        if event == self.transform_event:
+            return
+
+        self.transform_event = event
+        if self.transform_event_responder:
+            renpy.display.render.redraw(self, 0)
+
+    def _hide(self, st, at, kind):
+        """
+        Returns None if this displayable is ready to be hidden, or
+        a replacement displayable if it doesn't want to be hidden
+        quite yet. Kind is either "hide" or "replaced".
+        """
+                
+        return None
+
+    def _show(self):
+        """
+        Called when the displayable is added to a scene list.
+        """
+
+    def _get_parameterized(self):
+        """
+        If this is a ImageReference to a parameterized image, return
+        the get_parameterized() of the parameterized image. Otherwise,
+        return this displayable.
+        """
+
+        return self
+
+    def _change_transform_child(self, child):
+        """
+        If this is a transform, makes a copy of the transform and sets
+        the child of the innermost transform to this. Otherwise,
+        simply returns child.
+        """
+
+        return child
+
+    
+
+class SceneListEntry(renpy.object.Object):
+    """
+    Represents a scene list entry. Since this was replacing a tuple,
+    it should be treated as immutable after its initial creation.
+    """
+    
+    def __init__(self, tag, zorder, show_time, animation_time, displayable, name):
+        self.tag = tag
+        self.zorder = zorder
+        self.show_time = show_time
+        self.animation_time = animation_time
+        self.displayable = displayable
+        self.name = name
+        
+    def __iter__(self):
+        return iter((self.tag, self.zorder, self.show_time, self.animation_time, self.displayable))
+
+    def __getitem__(self, index):
+        return (self.tag, self.zorder, self.show_time, self.animation_time, self.displayable)[index]
+    
+    def __repr__(self):
+        return "<SLE: %r %r %r>" % (self.tag, self.name, self.displayable)
+    
+    def copy(self):
+        return SceneListEntry(
+            self.tag,
+            self.zorder,
+            self.show_time,
+            self.animation_time,
+            self.displayable,
+            self.name)
+            
+    def update_time(self, time):
+
+        rv = self
+        
+        if self.show_time is None or self.animation_time is None:
+            rv = self.copy()
+            rv.show_time = rv.show_time or time
+            rv.animation_time = rv.animation_time or time
+
+        return rv
+    
+    
+class SceneLists(renpy.object.Object):
+    """
+    This stores the current scene lists that are being used to display
+    things to the user. 
+    """
+
+    __version__ = 6
+    
+    def after_setstate(self):
+        for i in renpy.config.layers + renpy.config.top_layers:
+            if i not in self.layers:
+                self.layers[i] = [ ]
+                self.at_list[i] = { }
+                self.layer_at_list[i] = (None, [ ])
+                
+    def after_upgrade(self, version):
+
+        if version < 1:
+
+            self.at_list = { }
+            self.layer_at_list = { }
+
+            for i in renpy.config.layers + renpy.config.top_layers:
+                self.at_list[i] = { }
+                self.layer_at_list[i] = (None, [ ])
+
+        if version < 3:
+            self.shown_window = False 
+
+        if version < 4:
+            for k in self.layers:
+                self.layers[k] = [ SceneListEntry(*(i + (None,)) ) for i in self.layers[k] ]
+
+            self.additional_transient = [ ]
+
+        if version < 5:
+            self.drag_group = None
+
+        if version < 6:
+            self.shown = self.image_predict_info 
+            
+    def __init__(self, oldsl, shown):
+
+        super(SceneLists, self).__init__()
+        
+        # Has a window been shown as part of these scene lists?
+        self.shown_window = False
+        
+        # A map from layer name -> list(SceneListEntry)
+        self.layers = { }
+
+        # A map from layer name -> tag -> at_list associated with that tag.
+        self.at_list = { }
+
+        # A map from layer to (star time, at_list), where the at list has
+        # been applied to the layer as a whole.
+        self.layer_at_list = { }
+
+        # The current shown images,
+        self.shown = shown
+
+        # A list of (layer, tag) pairs that are considered to be
+        # transient.
+        self.additional_transient = [ ]
+
+        # Either None, or a DragGroup that's used as the default for
+        # drags with names.
+        self.drag_group = None
+        
+        if oldsl:
+
+            for i in renpy.config.layers + renpy.config.top_layers:
+
+                try:
+                    self.layers[i] = oldsl.layers[i][:]
+                except KeyError:
+                    self.layers[i] = [ ]
+                
+                if i in oldsl.at_list:
+                    self.at_list[i] = oldsl.at_list[i].copy()
+                    self.layer_at_list[i] = oldsl.layer_at_list[i]
+                else:
+                    self.at_list[i] = { }
+                    self.layer_at_list[i] = (None, [ ])
+                
+            for i in renpy.config.overlay_layers:
+                self.clear(i)
+
+            self.replace_transient()
+
+            self.focused = None
+
+            self.drag_group = oldsl.drag_group
+            
+        else:
+            for i in renpy.config.layers + renpy.config.top_layers:
+                self.layers[i] = [ ]
+                self.at_list[i] = { }
+                self.layer_at_list[i] = (None, [ ])
+                
+            self.music = None
+            self.focused = None
+
+    def replace_transient(self):
+        """
+        Replaces the contents of the transient display list with
+        a copy of the master display list. This is used after a
+        scene is displayed to get rid of transitions and interface
+        elements.
+        """
+
+        for i in renpy.config.transient_layers:
+            self.clear(i, True)
+
+        for layer, tag in self.additional_transient:
+            self.remove(layer, tag)
+
+        self.additional_transient = [ ]
+            
+    def transient_is_empty(self):
+        """
+        This returns True if all transient layers are empty. This is
+        used by the rollback code, as we can't start a new rollback
+        if there is something in a transient layer (as things in the
+        transient layer may contain objects that cannot be pickled,
+        like lambdas.)
+        """
+
+        for i in renpy.config.transient_layers:
+            if self.layers[i]:
+                return False
+
+        return True
+
+    def transform_state(self, old_thing, new_thing):
+        """
+        If the old thing is a transform, then move the state of that transform
+        to the new thing.
+        """
+
+        if old_thing is None:
+            return new_thing
+
+        # Don't bother wrapping screens, as they can't be transformed.
+        if isinstance(new_thing, renpy.display.screen.ScreenDisplayable):
+            return new_thing
+        
+        old_transform = old_thing._get_parameterized()
+        if not isinstance(old_transform, renpy.display.motion.Transform):
+            return new_thing
+
+        new_transform = new_thing._get_parameterized()
+        if not isinstance(new_transform, renpy.display.motion.Transform):
+            new_thing = new_transform = renpy.display.motion.Transform(child=new_thing)
+        
+        new_transform.take_state(old_transform)
+        return new_thing
+
+
+    def find_index(self, layer, tag, zorder, behind):
+        """
+        This finds the spot in the named layer where we should insert the
+        displayable. It returns two things: an index at which the new thing
+        should be added, and an index at which the old thing should be hidden.
+        (Note that the indexes are relative to the current state of the list,
+        which may change on an add or remove.)
+        """
+
+        add_index = None
+        remove_index = None
+
+
+        for i, sle in enumerate(self.layers[layer]):
+
+            if add_index is None:
+            
+                if sle.zorder == zorder:
+                    if sle.tag and (sle.tag == tag or sle.tag in behind):
+                        add_index = i
+                        
+                elif sle.zorder > zorder:
+                    add_index = i
+
+                    
+            if remove_index is None:
+                if (sle.tag and sle.tag == tag) or sle.displayable == tag:
+                    remove_index = i
+
+            
+        if add_index is None:
+            add_index = len(self.layers[layer])
+        
+        return add_index, remove_index
+    
+
+    def add(self,
+            layer,
+            thing,
+            key=None,
+            zorder=0,
+            behind=[ ],
+            at_list=[ ],
+            name=None,
+            atl=None,
+            default_transform=None,
+            transient=False):
+        """
+        Adds something to this scene list. Some of these names are quite a bit
+        out of date.
+
+        `thing` - The displayable to add.
+
+        `key` - A string giving the tag associated with this thing.
+
+        `zorder` - Where to place this thing in the zorder, an integer
+        A greater value means closer to the user.
+
+        `behind` - A list of tags to place the thing behind.
+
+        `at_list` - The at_list associated with this
+        displayable. Counterintunitively, this is not actually
+        applied, but merely stored for future use.
+
+        `name` - The full name of the image being displayed. This is used for
+        image lookup.
+
+        `atl` - If not None, an atl block applied to the thing. (This actually is
+        applied here.)
+
+        `default_transform` - The default transform that is used to initialized
+        the values in the other transforms.        
+        """
+        
+        if not isinstance(thing, Displayable):
+            raise Exception("Attempting to show something that isn't a displayable:" + repr(thing))
+        
+        if layer not in self.layers:
+            raise Exception("Trying to add something to non-existent layer '%s'." % layer)
+
+        if key:
+            self.remove_hide_replaced(layer, key)
+            self.at_list[layer][key] = at_list
+
+        if key and name:
+            self.shown.predict_show(layer, name)
+
+        if transient:
+            self.additional_transient.append((layer, key))
+            
+        l = self.layers[layer]
+        
+        if atl:
+            thing = renpy.display.motion.ATLTransform(atl, child=thing)
+
+        add_index, remove_index = self.find_index(layer, key, zorder, behind)
+
+        at = None
+        st = None
+
+        if remove_index is not None:
+            sle = l[remove_index]            
+            at = sle.animation_time            
+            old = sle.displayable
+            
+            if (not atl and
+                not at_list and
+                renpy.config.keep_running_transform and
+                isinstance(old, renpy.display.motion.Transform)):
+
+                thing = sle.displayable._change_transform_child(thing)
+            else:
+                thing = self.transform_state(l[remove_index].displayable, thing)
+
+            thing.set_transform_event("replace")
+            thing._show()
+            
+        else:            
+
+            if not isinstance(thing, renpy.display.motion.Transform):
+                thing = self.transform_state(default_transform, thing)
+                
+            thing.set_transform_event("show")
+            thing._show()
+
+        sle = SceneListEntry(key, zorder, st, at, thing, name)
+        l.insert(add_index, sle)
+
+        if remove_index is not None: 
+            if add_index <= remove_index:
+                remove_index += 1
+
+            self.hide_or_replace(layer, remove_index, "replaced")
+
+    def hide_or_replace(self, layer, index, prefix):
+        """
+        Hides or replaces the scene list entry at the given
+        index. `prefix` is a prefix that is used if the entry
+        decides it doesn't want to be hidden quite yet.
+        """
+
+        if index is None:
+            return
+        
+        l = self.layers[layer]
+        oldsle = l[index]
+
+        now = get_time()
+
+        st = oldsle.show_time or now
+        at = oldsle.animation_time or now
+
+        if oldsle.tag:
+        
+            d = oldsle.displayable._hide(now - st, now - at, prefix)
+            
+            # _hide can mutate the layers, so we need to recompute 
+            # index.            
+            index = l.index(oldsle)
+
+            if d is not None:
+                
+                sle = SceneListEntry(
+                    prefix + "$" + oldsle.tag,
+                    oldsle.zorder,
+                    st,
+                    at,
+                    d,
+                    None)
+
+                l[index] = sle
+
+                return
+
+        l.pop(index)
+
+    def get_all_displayables(self):
+        """
+        Gets all displayables reachable from this scene list.
+        """
+        
+        rv = [ ]
+        for l in self.layers.values():
+            for sle in l:
+                rv.append(sle.displayable)
+                
+        return rv
+
+    def remove_above(self, layer, thing):
+        """
+        Removes everything on the layer that is closer to the user
+        than thing, which may be either a tag or a displayable. Thing must
+        be displayed, or everything will be removed.
+        """
+        
+        for i in reversed(range(len(self.layers[layer]))):
+
+            sle = self.layers[layer][i]
+
+            if thing:
+                if sle.tag == thing or sle.displayable == thing:
+                    break
+
+            if sle.tag and "$" in sle.tag:
+                continue
+
+            self.hide_or_replace(layer, i, "hide")
+            
+    def remove(self, layer, thing):
+        """
+        Thing is either a key or a displayable. This iterates through the
+        named layer, searching for entries matching the thing.
+        When they are found, they are removed from the displaylist.
+
+        It's not an error to remove something that isn't in the layer in
+        the first place.
+        """
+
+        if layer not in self.layers:
+            raise Exception("Trying to remove something from non-existent layer '%s'." % layer)
+
+        _add_index, remove_index = self.find_index(layer, thing, 0, [ ])
+
+        if remove_index is not None:
+            tag = self.layers[layer][remove_index].tag
+
+            if tag:
+                self.shown.predict_hide(layer, (tag,))
+                self.at_list[layer].pop(tag, None)
+            
+            self.hide_or_replace(layer, remove_index, "hide")
+
+    def clear(self, layer, hide=False):
+        """
+        Clears the named layer, making it empty.
+
+        If hide is True, then objects are hidden. Otherwise, they are
+        totally wiped out.
+        """
+
+        if not hide:
+            self.layers[layer] = [ ]
+
+        else:
+
+            # Have to iterate in reverse order, since otherwise
+            # the indexes might change.
+            for i in reversed(range(len(self.layers[layer]))):
+                self.hide_or_replace(layer, i, hide)
+
+        self.at_list[layer].clear()
+        self.shown.predict_scene(layer)
+        self.layer_at_list[layer] = (None, [ ])
+
+    def set_layer_at_list(self, layer, at_list):
+        self.layer_at_list[layer] = (None, list(at_list))
+        
+    def set_times(self, time):
+        """
+        This finds entries with a time of None, and replaces that
+        time with the given time.
+        """
+
+        for l, (t, list) in list(self.layer_at_list.items()): #@ReservedAssignment
+            self.layer_at_list[l] = (t or time, list)
+        
+        for l, ll in self.layers.items():
+            self.layers[l] = [ i.update_time(time) for i in ll ]
+            
+    def showing(self, layer, name):
+        """
+        Returns true if something with the prefix of the given name
+        is found in the scene list.
+        """
+
+        return self.shown.showing(layer, name)
+
+    def make_layer(self, layer, properties):
+        """
+        Creates a Fixed with the given layer name and scene_list.
+        """
+
+        rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **properties)
+        rv.append_scene_list(self.layers[layer])
+
+        time, at_list = self.layer_at_list[layer]
+
+        if at_list:
+            for a in at_list:
+                
+                if isinstance(a, renpy.display.motion.Transform):
+                    rv = a(child=rv)
+                else:
+                    rv = a(rv)
+
+                f = renpy.display.layout.MultiBox(layout='fixed')
+                f.add(rv, time, time)
+                rv = f
+
+        rv.layer_name = layer
+        return rv
+
+    def remove_hide_replaced(self, layer, tag):
+        """
+        Removes things that are hiding or replaced, that have the given
+        tag.
+        """
+
+        hide_tag = "hide$" + tag
+        replaced_tag = "replaced$" + tag
+
+        l = self.layers[layer]
+        self.layers[layer] = [ i for i in l if i.tag != hide_tag and i.tag != replaced_tag ]
+        
+    def remove_hidden(self):
+        """
+        Goes through all of the layers, and removes things that are
+        hidden and are no longer being kept alive by their hide
+        methods.
+        """
+
+        now = get_time()
+        
+        for l in self.layers:
+            newl = [ ]
+
+            for sle in self.layers[l]:
+
+                if sle.tag:
+
+                    if sle.tag.startswith("hide$"):
+                        d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "hide")
+                        if not d:
+                            continue
+
+                    elif sle.tag.startswith("replaced$"):
+                        d = sle.displayable._hide(now - sle.show_time, now - sle.animation_time, "replaced")
+                        if not d:
+                            continue
+                        
+                newl.append(sle)
+                        
+            self.layers[l] = newl  
+
+    def get_displayable_by_tag(self, layer, tag):
+        """
+        Returns the displayable on the layer with the given tag, or None
+        if no such displayable exists. Note that this will usually return
+        a Transform.
+        """
+
+        if layer not in self.layers:
+            raise Exception("Unknown layer %r." % layer)
+
+        for sle in self.layers[layer]:
+            if sle.tag == tag:
+                return sle.displayable
+
+        return None
+
+    def get_displayable_by_name(self, layer, name):
+        """
+        Returns the displayable on the layer with the given tag, or None
+        if no such displayable exists. Note that this will usually return
+        a Transform.
+        """
+
+        if layer not in self.layers:
+            raise Exception("Unknown layer %r." % layer)
+
+        for sle in self.layers[layer]:
+
+            if sle.name == name:
+                return sle.displayable
+
+        return None
+
+    
+def scene_lists(index=-1):
+    """
+    Returns either the current scenelists object, or the one for the
+    context at the given index.
+    """
+
+    return renpy.game.context(index).scene_lists
+   
+
+class Interface(object):
+    """
+    This represents the user interface that interacts with the user.
+    It manages the Display objects that display things to the user, and
+    also handles accepting and responding to user input.
+
+    @ivar display: The display that we used to display the screen.
+
+    @ivar profile_time: The time of the last profiling.
+
+    @ivar screenshot: A screenshot, or None if no screenshot has been
+    taken.
+
+    @ivar old_scene: The last thing that was displayed to the screen.
+
+    @ivar transition: A map from layer name to the transition that will
+    be applied the next time interact restarts.
+
+    @ivar transition_time: A map from layer name to the time the transition
+    involving that layer started.
+
+    @ivar transition_from: A map from layer name to the scene that we're
+    transitioning from on that layer.
+    
+    @ivar suppress_transition: If True, then the next transition will not
+    happen.
+
+    @ivar force_redraw: If True, a redraw is forced.
+
+    @ivar restart_interaction: If True, the current interaction will
+    be restarted.
+
+    @ivar pushed_event: If not None, an event that was pushed back
+    onto the stack.
+
+    @ivar mouse: The name of the mouse cursor to use during the current
+    interaction.
+
+    @ivar ticks: The number of 20hz ticks.
+
+    @ivar frame_time: The time at which we began drawing this frame.
+
+    @ivar interact_time: The time of the start of the first frame of the current interact_core.
+
+    @ivar time_event: A singleton ignored event.
+
+    @ivar event_time: The time of the current event.
+
+    @ivar timeout_time: The time at which the timeout will occur.
+    """
+
+    def __init__(self):
+        self.screenshot = None
+        self.old_scene = { }
+        self.transition = { }
+        self.ongoing_transition = { }
+        self.transition_time = { }
+        self.transition_from = { }
+        self.suppress_transition = False
+        self.quick_quit = False
+        self.force_redraw = False
+        self.restart_interaction = False
+        self.pushed_event = None
+        self.ticks = 0
+        self.mouse = 'default'
+        self.timeout_time = None
+        self.last_event = None
+        self.current_context = None
+        self.roll_forward = None
+        
+        # Things to be preloaded.
+        self.preloads = [ ]
+
+        # The time at which this draw occurs.
+        self.frame_time = 0
+
+        # The time when this interaction occured.
+        self.interact_time = None
+
+        # The time we last tried to quit.
+        self.quit_time = 0
+        
+        self.time_event = pygame.event.Event(TIMEEVENT)
+        self.redraw_event = pygame.event.Event(REDRAW)
+
+        # Are we focused?
+        self.focused = True
+        
+        # Properties for each layer.
+        self.layer_properties = { }
+
+        # Have we shown the window this interaction?
+        self.shown_window = False
+        
+        # Are we in fullscren mode?
+        self.fullscreen = False
+        
+        for layer in renpy.config.layers + renpy.config.top_layers:
+            if layer in renpy.config.layer_clipping:
+                x, y, w, h = renpy.config.layer_clipping[layer]
+                self.layer_properties[layer] = dict(
+                    xpos = x,
+                    xanchor = 0,
+                    ypos = y,
+                    yanchor = 0,
+                    xmaximum = w,
+                    ymaximum = h,
+                    xminimum = w,
+                    yminimum = h,
+                    clipping = True,
+                    )
+
+            else:
+                self.layer_properties[layer] = dict()
+                
+
+        # A stack giving the values of self.transition and self.transition_time
+        # for contexts outside the current one. This is used to restore those
+        # in the case where nothing has changed in the new context.
+        self.transition_info_stack = [ ]
+
+        # The time when the event was dispatched.
+        self.event_time = 0
+
+        # The time we saw the last mouse event.
+        self.mouse_event_time = None
+
+        # Should we show the mouse?
+        self.show_mouse = True
+
+        # Should we reset the display?
+        self.display_reset = False
+        
+        # The last size we were resized to. This lets us debounce the 
+        # VIDEORESIZE event.
+        self.last_resize = None
+        
+        # Ensure that we kill off the presplash.
+        renpy.display.presplash.end()
+
+        # Initialize pygame.
+        if pygame.version.vernum < (1, 8, 1):
+            raise Exception("Ren'Py requires pygame 1.8.1 to run.")
+
+        try:
+            import pygame.macosx as macosx
+            macosx.init() #@UndefinedVariable
+        except:
+            pass
+
+        try:
+            macosx.Video_AutoInit() #@UndefinedVariable
+        except:
+            pass
+        
+        pygame.font.init()
+        renpy.audio.audio.init()
+        renpy.display.joystick.init()
+        pygame.display.init()
+        
+        # Init timing.
+        init_time()
+        self.profile_time = get_time()
+        self.mouse_event_time = get_time()
+        
+        # The current window caption.
+        self.window_caption = None
+
+        renpy.game.interface = self
+        renpy.display.interface = self
+        
+        # Are we in safe mode, from holding down shift at start?
+        self.safe_mode = False
+        if renpy.first_utter_start and (pygame.key.get_mods() & pygame.KMOD_SHIFT):
+            self.safe_mode = True
+
+        # Setup the video mode.
+        self.set_mode()
+
+        # Load the image fonts.
+        renpy.text.font.load_image_fonts()
+
+        # Setup the android keymap.
+        if android is not None:
+            android.map_key(android.KEYCODE_BACK, pygame.K_PAGEUP)
+            android.map_key(android.KEYCODE_MENU, pygame.K_ESCAPE)
+
+        # Double check, since at least on Linux, we can't set safe_mode until
+        # the window maps.
+        if renpy.first_utter_start and (pygame.key.get_mods() & pygame.KMOD_SHIFT):
+            self.safe_mode = True
+            
+        # Setup periodic event.
+        pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)
+
+        # Don't grab the screen.
+        pygame.event.set_grab(False)
+
+        # Do we need a background screenshot?
+        self.bgscreenshot_needed = False
+
+        # Event used to signal background screenshot taken.
+        self.bgscreenshot_event = threading.Event()
+
+        # The background screenshot surface.
+        self.bgscreenshot_surface = None
+
+        
+    def post_init(self):
+        # Setup.
+
+        # Needed for Unity.
+        wmclass = renpy.config.save_directory or os.path.basename(sys.argv[0])
+        os.environ['SDL_VIDEO_X11_WMCLASS'] = wmclass
+        
+        self.set_window_caption(force=True)
+        self.set_icon()
+    
+        if renpy.config.key_repeat is not None:
+            delay, repeat_delay = renpy.config.key_repeat
+            pygame.key.set_repeat(int(1000 * delay), int(1000 * repeat_delay))
+    
+    def set_icon(self):
+        """
+        This is called to set up the window icon.
+        """
+
+        # Window icon.
+        icon = renpy.config.window_icon
+
+        if renpy.windows and renpy.config.windows_icon:
+            icon = renpy.config.windows_icon
+            
+        if icon:
+
+            im = renpy.display.scale.image_load_unscaled(
+                renpy.loader.load(icon),
+                icon,
+                convert=False,
+                )
+
+            # Convert the aspect ratio to be square.
+            iw, ih = im.get_size()
+            imax = max(iw, ih)
+            square_im = renpy.display.pgrender.surface_unscaled((imax, imax), True)
+            square_im.blit(im, ( (imax-iw)/2, (imax-ih)/2 ))
+            im = square_im
+
+            if renpy.windows and im.get_size() != (32, 32):
+                im = renpy.display.scale.real_smoothscale(im, (32, 32))
+
+            pygame.display.set_icon(im)
+
+            
+    def set_window_caption(self, force=False):
+        caption = renpy.config.window_title + renpy.store._window_subtitle
+        if not force and caption == self.window_caption:
+            return
+
+        self.window_caption = caption
+        pygame.display.set_caption(caption.encode("utf-8"))
+        
+    def iconify(self):
+        pygame.display.iconify()
+
+    def get_draw_constructors(self):
+        """
+        Figures out the list of draw constructors to try.
+        """
+        
+        renderer = renpy.game.preferences.renderer
+        renderer = os.environ.get("RENPY_RENDERER", renderer)
+        
+        if self.safe_mode:
+            renderer = "sw"
+
+        renpy.config.renderer = renderer
+
+        if renderer == "auto":
+            if renpy.windows:
+                renderers = [ "gl", "angle", "sw" ]
+            else:
+                renderers = [ "gl", "sw" ]                
+        else:
+            renderers = [ renderer, "sw" ]
+            
+        draw_objects = { }
+
+        def make_draw(name, mod, cls, *args):
+            if name not in renderers:
+                return False
+            
+            try:
+                __import__(mod)
+                module = sys.modules[mod]
+                draw_class = getattr(module, cls)                
+                draw_objects[name] = draw_class(*args)
+                return True
+
+            except:
+                renpy.display.log.write("Couldn't import {0} renderer:".format(name))
+                renpy.display.log.exception()
+
+                return False
+
+        if renpy.windows:
+            has_angle = make_draw("angle", "renpy.angle.gldraw", "GLDraw")
+        else:
+            has_angle = False
+        
+        make_draw("gl", "renpy.gl.gldraw", "GLDraw", not has_angle)
+        make_draw("sw", "renpy.display.swdraw", "SWDraw")
+
+        rv = [ ]
+
+        def append_draw(name):
+            if name in draw_objects:
+                rv.append(draw_objects[name])
+            else:
+                renpy.display.log.write("Unknown renderer: {0}".format(name))
+
+        for i in renderers:
+            append_draw(i)
+
+        return rv
+
+
+    def kill_textures(self):
+        renpy.display.render.free_memory()
+        renpy.text.text.layout_cache_clear()
+
+    def kill_textures_and_surfaces(self):
+        """
+        Kill all textures and surfaces that are loaded.
+        """
+
+        self.kill_textures()
+        
+        renpy.display.im.cache.clear()
+        renpy.display.module.bo_cache = None       
+        
+    def set_mode(self, physical_size=None):
+        """
+        This sets the video mode. It also picks the draw object.
+        """
+        
+        # Ensure that we kill off the movie when changing screen res.
+        if renpy.display.draw and renpy.display.draw.info["renderer"] == "sw":
+            renpy.display.video.movie_stop(clear=False)
+
+        if self.display_reset:
+            renpy.display.draw.deinit()
+
+            if renpy.display.draw.info["renderer"] == "angle":
+                renpy.display.draw.quit()
+
+            renpy.display.render.free_memory()
+            renpy.display.im.cache.clear()
+            renpy.text.text.layout_cache_clear()
+
+            renpy.display.module.bo_cache = None
+            
+            self.kill_textures_and_surfaces()
+                        
+        self.display_reset = False
+
+        virtual_size = (renpy.config.screen_width, renpy.config.screen_height)
+
+        if physical_size is None:
+            if renpy.android or renpy.game.preferences.physical_size is None: #@UndefinedVariable
+                physical_size = (renpy.config.screen_width, renpy.config.screen_height)
+            else:
+                physical_size = renpy.game.preferences.physical_size
+                
+        # Setup screen.
+        fullscreen = renpy.game.preferences.fullscreen
+        
+        # If we're in fullscreen mode, and changing to another mode, go to
+        # windowed mode first.
+        s = pygame.display.get_surface()
+        if s and (s.get_flags() & pygame.FULLSCREEN):
+            fullscreen = False
+            
+        old_fullscreen = self.fullscreen
+        self.fullscreen = fullscreen
+
+        if os.environ.get('RENPY_DISABLE_FULLSCREEN', False):
+            fullscreen = False
+            self.fullscreen = renpy.game.preferences.fullscreen
+        
+        if renpy.display.draw:
+            draws = [ renpy.display.draw ]
+        else:
+            draws = self.get_draw_constructors()
+
+        for draw in draws:
+            if draw.set_mode(virtual_size, physical_size, fullscreen):
+                renpy.display.draw = draw
+                break
+            else:
+                pygame.display.quit()
+        else:
+            # Ensure we don't get stuck in fullscreen.
+            renpy.game.preferences.fullscreen = False
+            raise Exception("Could not set video mode.")
+        
+        # Save the video size.
+        if renpy.config.save_physical_size and not fullscreen and not old_fullscreen: 
+            renpy.game.preferences.physical_size = renpy.display.draw.get_physical_size()
+       
+        if android:
+            android.init()
+        
+        # We need to redraw the (now blank) screen.
+        self.force_redraw = True
+
+        # Assume we have focus until told otherwise.
+        self.focused = True
+        
+        # Assume we're not minimized.
+        self.minimized = False
+
+        # Force an interaction restart.
+        self.restart_interaction = True
+
+
+    def draw_screen(self, root_widget, fullscreen_video, draw):
+        
+        surftree = renpy.display.render.render_screen(
+            root_widget,
+            renpy.config.screen_width,
+            renpy.config.screen_height,
+            )
+
+        if draw:
+            renpy.display.draw.draw_screen(surftree, fullscreen_video)
+
+        renpy.display.render.mark_sweep()
+        renpy.display.focus.take_focuses()
+
+        self.surftree = surftree
+        self.fullscreen_video = fullscreen_video
+
+
+    def take_screenshot(self, scale, background=False):
+        """
+        This takes a screenshot of the current screen, and stores it so
+        that it can gotten using get_screenshot()
+        
+        `background`
+           If true, we're in a background thread. So queue the request
+           until it can be handled by the main thread.
+        """
+
+        if background:
+            self.bgscreenshot_event.clear()
+            self.bgscreenshot_needed = True
+            self.bgscreenshot_event.wait()
+
+            window = self.bgscreenshot_surface
+            self.bgscreenshot_surface = None
+
+        else:
+
+            window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
+        
+        surf = renpy.display.pgrender.copy_surface(window, True)
+        surf = renpy.display.scale.smoothscale(surf, scale)
+        surf = surf.convert()
+        
+        sio = io.StringIO()
+        renpy.display.module.save_png(surf, sio, 0)
+        self.screenshot = sio.getvalue()
+        sio.close()
+        
+        
+    def save_screenshot(self, filename):
+        """
+        Saves a full-size screenshot in the given filename.
+        """
+
+        window = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
+
+        if renpy.config.screenshot_crop:
+            window = window.subsurface(renpy.config.screenshot_crop)
+        
+        try:
+            renpy.display.scale.image_save_unscaled(window, filename)
+        except:
+            if renpy.config.debug:
+                raise
+            pass
+        
+        
+    def get_screenshot(self):
+        """
+        Gets the current screenshot, as a string. Returns None if there isn't
+        a current screenshot.
+        """
+
+        rv = self.screenshot
+
+        if not rv:
+            self.take_screenshot((renpy.config.thumbnail_width, renpy.config.thumbnail_height))
+            rv = self.screenshot
+            self.lose_screenshot()
+
+        return rv
+
+    
+    def lose_screenshot(self):
+        """
+        This deallocates the saved screenshot.
+        """
+
+        self.screenshot = None
+
+        
+    def show_window(self):
+
+        if not renpy.store._window:
+            return
+
+        if renpy.game.context().scene_lists.shown_window:
+            return
+
+        if renpy.config.empty_window:
+            renpy.config.empty_window()
+            
+    def do_with(self, trans, paired, clear=False):
+        
+        if renpy.config.with_callback:
+            trans = renpy.config.with_callback(trans, paired)
+
+        if (not trans) or self.suppress_transition:
+            self.with_none()
+            return False
+        else:
+            self.set_transition(trans)
+            return self.interact(trans_pause=True,
+                                 suppress_overlay=not renpy.config.overlay_during_with,
+                                 mouse='with',
+                                 clear=clear)
+
+    def with_none(self):
+        """
+        Implements the with None command, which sets the scene we will
+        be transitioning from.
+        """
+
+        renpy.exports.say_attributes = None
+
+        # Show the window, if that's necessary.
+        self.show_window()
+
+        # Compute the overlay.
+        self.compute_overlay()
+
+        scene_lists = renpy.game.context().scene_lists
+
+        # Compute the scene.
+        self.old_scene = self.compute_scene(scene_lists)        
+
+        # Get rid of transient things.
+
+        for i in renpy.config.overlay_layers:
+            scene_lists.clear(i)
+
+        scene_lists.replace_transient()
+        scene_lists.shown_window = False
+
+        
+    def set_transition(self, transition, layer=None, force=False):
+        """
+        Sets the transition that will be performed as part of the next
+        interaction.
+        """
+
+        if self.suppress_transition and not force:
+            return
+
+        if transition is None:
+            self.transition.pop(layer, None)
+        else:
+            self.transition[layer] = transition
+        
+
+    def event_peek(self):
+        """
+        This peeks the next event. It returns None if no event exists.
+        """
+
+        if self.pushed_event:
+            return self.pushed_event
+
+        ev = pygame.event.poll()
+
+        if ev.type == pygame.NOEVENT:
+            # Seems to prevent the CPU from speeding up.
+            renpy.display.draw.event_peek_sleep()
+            return None
+
+        self.pushed_event = ev
+
+        return ev
+
+    def event_poll(self):
+        """
+        Called to busy-wait for an event while we're waiting to
+        redraw a frame.
+        """
+        
+        if self.pushed_event:
+            rv = self.pushed_event
+            self.pushed_event = None
+        else:
+            rv = pygame.event.poll()
+
+        self.last_event = rv
+
+        return rv
+            
+
+    def event_wait(self):
+        """
+        This is in its own function so that we can track in the
+        profiler how much time is spent in interact.
+        """
+
+        if self.pushed_event:
+            rv = self.pushed_event
+            self.pushed_event = None
+            self.last_event = rv
+            return rv
+
+        # Handle a request for a background screenshot.
+        if self.bgscreenshot_needed:
+            self.bgscreenshot_needed = False
+            self.bgscreenshot_surface = renpy.display.draw.screenshot(self.surftree, self.fullscreen_video)
+            self.bgscreenshot_event.set()
+            
+        try:
+            cpu_idle.set()            
+            ev = pygame.event.wait()
+        finally:
+            cpu_idle.clear()
+
+        self.last_event = ev
+
+        return ev
+
+    
+    def compute_overlay(self):
+
+        if renpy.store.suppress_overlay:
+            return
+        
+        # Figure out what the overlay layer should look like.
+        renpy.ui.layer("overlay")
+
+        for i in renpy.config.overlay_functions:
+            i()
+
+        if renpy.game.context().scene_lists.shown_window:
+            for i in renpy.config.window_overlay_functions:
+                i()
+                
+        renpy.ui.close()
+        
+    
+    def compute_scene(self, scene_lists):
+        """
+        This converts scene lists into a dictionary mapping layer
+        name to a Fixed containing that layer.
+        """
+
+        rv = { }
+
+        for layer in renpy.config.layers + renpy.config.top_layers:
+            rv[layer] = scene_lists.make_layer(layer, self.layer_properties[layer])
+
+        root = renpy.display.layout.MultiBox(layout='fixed')
+        root.layers = { }
+
+        for layer in renpy.config.layers:
+            root.layers[layer] = rv[layer]
+            root.add(rv[layer])
+        rv[None] = root
+             
+        return rv
+
+
+    def quit_event(self):
+        """
+        This is called to handle the user invoking a quit.
+        """
+
+        if self.quit_time > (time.time() - .75):
+            raise renpy.game.QuitException()
+
+        if renpy.config.quit_action is not None:
+            self.quit_time = time.time()
+
+            # Make the screen more suitable for interactions.
+            renpy.exports.movie_stop(only_fullscreen=True)
+            renpy.store.mouse_visible = True
+
+            renpy.display.behavior.run(renpy.config.quit_action)
+        else:
+            raise renpy.game.QuitException()
+
+        
+    def get_mouse_info(self):
+        # Figure out if the mouse visibility algorithm is hiding the mouse.
+        if self.mouse_event_time + renpy.config.mouse_hide_time < renpy.display.core.get_time():
+            visible = False
+        else:
+            visible = renpy.store.mouse_visible and (not renpy.game.less_mouse)
+            
+        visible = visible and self.show_mouse
+
+        # If not visible, hide the mouse.
+        if not visible:
+            return False, 0, 0, None
+        
+        # Deal with a hardware mouse, the easy way.
+        if not renpy.config.mouse:
+            return True, 0, 0, None
+
+        # Deal with the mouse going offscreen.
+        if not self.focused:
+            return False, 0, 0, None
+        
+        mouse_kind = renpy.display.focus.get_mouse() or self.mouse 
+        
+        # Figure out the mouse animation.
+        if mouse_kind in renpy.config.mouse:
+            anim = renpy.config.mouse[mouse_kind]
+        else:
+            anim = renpy.config.mouse[getattr(renpy.store, 'default_mouse', 'default')]
+
+        img, x, y = anim[self.ticks % len(anim)]
+        tex = renpy.display.im.load_image(img)
+
+        return False, x, y, tex
+
+    def drawn_since(self, seconds_ago):
+        """
+        Returns true if the screen has been drawn in the last `seconds_ago`,
+        and false otherwise.
+        """
+
+        return (get_time() - self.frame_time) <= seconds_ago
+
+    def android_check_suspend(self):
+        
+        if android.check_pause():
+
+            android.sound.pause_all()
+
+            pygame.time.set_timer(PERIODIC, 0)
+            pygame.time.set_timer(REDRAW, 0)
+            pygame.time.set_timer(TIMEEVENT, 0)
+
+            # The game has to be saved.
+            renpy.loadsave.save("_reload-1")
+
+            android.wait_for_resume()
+
+            # Since we came back to life, we can get rid of the
+            # auto-reload.
+            renpy.loadsave.unlink_save("_reload-1")
+
+            pygame.time.set_timer(PERIODIC, PERIODIC_INTERVAL)
+
+            android.sound.unpause_all()
+            
+    def iconified(self):
+        """
+        Called when we become an icon.
+        """
+
+        if self.minimized:
+            return
+
+        self.minimized = True
+
+        renpy.display.log.write("The window was minimized.")
+        
+    
+    def restored(self):
+        """
+        Called when we are restored from being an icon.
+        """
+        
+        # This is necessary on Windows/DirectX/Angle, as otherwise we get
+        # a blank screen.
+
+        if not self.minimized:
+            return
+        
+        self.minimized = False
+
+        renpy.display.log.write("The window was restored.")
+
+        if renpy.windows:
+            self.display_reset = True
+            self.set_mode(self.last_resize)
+
+    def enter_context(self):
+        """
+        Called when we enter a new context.
+        """
+    
+        # Stop ongoing transitions.
+        self.ongoing_transition.clear()
+        self.transition_from.clear()
+        self.transition_time.clear()
+    
+    def post_time_event(self):
+        """
+        Posts a time_event object to the queue.
+        """
+        
+        try:
+            pygame.event.post(self.time_event)
+        except:
+            pass
+    
+    def interact(self, clear=True, suppress_window=False, **kwargs):
+        """
+        This handles an interaction, restarting it if necessary. All of the
+        keyword arguments are passed off to interact_core.
+        """
+
+        # Cancel magic error reporting.
+        renpy.bootstrap.report_error = None
+        
+        context = renpy.game.context()
+        
+        if context.interacting:
+            raise Exception("Cannot start an interaction in the middle of an interaction, without creating a new context.")
+
+        context.interacting = True
+              
+                
+        # Show a missing window.
+        if not suppress_window:
+            self.show_window()
+        
+        # These things can be done once per interaction.
+
+        preloads = self.preloads
+        self.preloads = [ ]
+
+        try:
+            renpy.game.after_rollback = False
+
+            for i in renpy.config.start_interact_callbacks:
+                i()
+
+            repeat = True
+
+            while repeat:
+                repeat, rv = self.interact_core(preloads=preloads, **kwargs)
+
+            return rv
+        
+        finally:
+
+            context.interacting = False
+            
+            # Clean out transient stuff at the end of an interaction.
+            if clear:
+                scene_lists = renpy.game.context().scene_lists
+                scene_lists.replace_transient()
+
+            self.ongoing_transition = { }
+            self.transition_time = { }
+            self.transition_from = { }
+                
+            self.restart_interaction = True
+
+            renpy.game.context().scene_lists.shown_window = False
+            
+    def interact_core(self,
+                      show_mouse=True,
+                      trans_pause=False,
+                      suppress_overlay=False,
+                      suppress_underlay=False,
+                      mouse='default',
+                      preloads=[],
+                      roll_forward=None,
+                      ):
+
+        """
+        This handles one cycle of displaying an image to the user,
+        and then responding to user input.
+
+        @param show_mouse: Should the mouse be shown during this
+        interaction? Only advisory, and usually doesn't work.
+
+        @param trans_pause: If given, we must have a transition. Should we
+        add a pause behavior during the transition?
+
+        @param suppress_overlay: This suppresses the display of the overlay.
+        @param suppress_underlay: This suppresses the display of the underlay.
+        """
+
+        self.roll_forward = roll_forward
+        self.show_mouse = show_mouse
+        
+        suppress_transition = renpy.config.skipping or renpy.game.less_updates
+
+        # The global one.
+        self.suppress_transition = False
+        
+        # Figure out transitions.
+        for k in self.transition:
+            if k not in self.old_scene:
+                continue
+            
+            self.ongoing_transition[k] = self.transition[k]            
+            self.transition_from[k] = self.old_scene[k]
+            self.transition_time[k] = None
+                    
+        self.transition.clear()
+
+        if suppress_transition:
+            self.ongoing_transition.clear()
+            self.transition_from.clear()
+            self.transition_time.clear()
+ 
+        ## Safety condition, prevents deadlocks.
+        if trans_pause:
+            if not self.ongoing_transition:
+                return False, None
+            if None not in self.ongoing_transition:
+                return False, None
+            if suppress_transition:
+                return False, None
+            if not self.old_scene:
+                return False, None
+            
+        # Check to see if the language has changed.
+        renpy.translation.check_language()
+            
+        # We just restarted.
+        self.restart_interaction = False
+
+        # Setup the mouse.
+        self.mouse = mouse
+
+        # The start and end times of this interaction.
+        start_time = get_time()
+        end_time = start_time
+
+        # frames = 0
+
+        for i in renpy.config.interact_callbacks:
+            i()
+
+        # Set the window caption.
+        self.set_window_caption()
+            
+        # Tick time forward.
+        renpy.display.im.cache.tick()
+        renpy.text.text.layout_cache_tick()
+        renpy.display.predict.reset()
+        
+        # Cleare the size groups.
+        renpy.display.layout.size_groups.clear()
+        
+        # Clear some events.
+        pygame.event.clear((pygame.MOUSEMOTION,
+                            PERIODIC,
+                            TIMEEVENT,
+                            REDRAW))
+
+        # Add a single TIMEEVENT to the queue.
+        self.post_time_event()
+        
+        # Figure out the scene list we want to show.        
+        scene_lists = renpy.game.context().scene_lists
+
+        # Remove the now-hidden things.
+        scene_lists.remove_hidden()
+
+        # Compute the overlay.
+        if not suppress_overlay:
+            self.compute_overlay()
+
+        # The root widget of everything that is displayed on the screen.
+        root_widget = renpy.display.layout.MultiBox(layout='fixed') 
+        root_widget.layers = { }
+
+        # A list of widgets that are roots of trees of widgets that are
+        # considered for focusing.
+        focus_roots = [ ]
+
+        # Add the underlay to the root widget.
+        if not suppress_underlay:
+            for i in renpy.config.underlay:
+                root_widget.add(i)
+                focus_roots.append(i)
+
+            if roll_forward is not None:
+                rfw = renpy.display.behavior.RollForward(roll_forward)
+                root_widget.add(rfw)
+                focus_roots.append(rfw)
+                
+        # Figure out the scene. (All of the layers, and the root.)
+        scene = self.compute_scene(scene_lists)
+
+        # If necessary, load all images here.
+        for w in scene.values():
+            try:
+                renpy.display.predict.displayable(w)
+            except:
+                pass
+
+        # The root widget of all of the layers.
+        layers_root = renpy.display.layout.MultiBox(layout='fixed')
+        layers_root.layers = { }
+
+        def add_layer(where, layer):
+
+            scene_layer = scene[layer]
+            focus_roots.append(scene_layer)
+
+            if (self.ongoing_transition.get(layer, None) and
+                not suppress_transition):
+
+                trans = self.ongoing_transition[layer](
+                    old_widget=self.transition_from[layer],
+                    new_widget=scene_layer)
+                                               
+                if not isinstance(trans, Displayable):
+                    raise Exception("Expected transition to be a displayable, not a %r" % trans)
+
+                transition_time = self.transition_time.get(layer, None)
+                
+                where.add(trans, transition_time, transition_time)
+                where.layers[layer] = trans
+                
+            else:
+                where.layers[layer] = scene_layer
+                where.add(scene_layer)
+
+        # Add layers (perhaps with transitions) to the layers root.
+        for layer in renpy.config.layers:
+            add_layer(layers_root, layer)
+                
+        # Add layers_root to root_widget, perhaps through a transition.
+        if (self.ongoing_transition.get(None, None) and
+            not suppress_transition):
+
+            old_root = renpy.display.layout.MultiBox(layout='fixed')
+            old_root.layers = { }
+
+            for layer in renpy.config.layers:
+                d = self.transition_from[None].layers[layer]
+                old_root.layers[layer] = d
+                old_root.add(d)
+
+            trans = self.ongoing_transition[None](
+                old_widget=old_root,
+                new_widget=layers_root)
+
+            if not isinstance(trans, Displayable):
+                raise Exception("Expected transition to be a displayable, not a %r" % trans)
+
+            trans._show()
+            
+            transition_time = self.transition_time.get(None, None)
+            root_widget.add(trans, transition_time, transition_time)
+
+            if trans_pause:
+                sb = renpy.display.behavior.SayBehavior()
+                root_widget.add(sb)
+                focus_roots.append(sb)
+
+                pb = renpy.display.behavior.PauseBehavior(trans.delay)
+                root_widget.add(pb, transition_time, transition_time)
+                focus_roots.append(pb)
+                
+        else:
+            root_widget.add(layers_root)
+
+        # Add top_layers to the root_widget.
+        for layer in renpy.config.top_layers:
+            add_layer(root_widget, layer)
+
+        prediction_coroutine = renpy.display.predict.prediction_coroutine(root_widget)
+        prediction_coroutine.send(None)
+            
+        # Clean out the registered adjustments.
+        renpy.display.behavior.adj_registered.clear()
+
+        # Clean up some movie-related things.
+        renpy.display.video.early_interact()
+
+        # Call per-interaction code for all widgets.
+        root_widget.visit_all(lambda i : i.per_interact())
+        
+        # Now, update various things regarding scenes and transitions,
+        # so we are ready for a new interaction or a restart.
+        self.old_scene = scene
+
+        # Okay, from here on we now have a single root widget (root_widget),
+        # which we will try to show to the user.
+
+        # Figure out what should be focused.
+        renpy.display.focus.before_interact(focus_roots)
+
+        # Redraw the screen.
+        renpy.display.render.process_redraws()
+        needs_redraw = True
+
+        # First pass through the while loop?
+        first_pass = True
+
+        # We don't yet know when the interaction began.
+        self.interact_time = None
+        
+        # We only want to do autosave once.
+        did_autosave = False
+        
+        old_timeout_time = None
+        old_redraw_time = None
+
+        rv = None
+
+        # Start sound.
+        renpy.audio.audio.interact()
+
+        # How long until we redraw.
+        redraw_in = 3600
+                
+        # Have we drawn a frame yet?
+        video_frame_drawn = False
+                
+        # This try block is used to force cleanup even on termination
+        # caused by an exception propagating through this function.
+        try: 
+
+            while rv is None:
+
+                # Check for a change in fullscreen preference.                
+                if self.fullscreen != renpy.game.preferences.fullscreen or self.display_reset:
+                    self.set_mode()
+                    needs_redraw = True
+
+                # Check for suspend.
+                if android:
+                    self.android_check_suspend()
+                    
+                # Redraw the screen.
+                if (self.force_redraw or
+                    ((first_pass or not pygame.event.peek(ALL_EVENTS)) and 
+                     renpy.display.draw.should_redraw(needs_redraw, first_pass))):
+
+                    self.force_redraw = False
+                    
+                    # If we have a movie, start showing it.
+                    fullscreen_video = renpy.display.video.interact()
+
+                    # Clean out the redraws, if we have to.
+                    # renpy.display.render.kill_redraws()
+                    
+                    # Draw the screen.
+                    self.frame_time = get_time()
+
+                    if not self.interact_time:
+                        self.interact_time = self.frame_time
+
+                    self.draw_screen(root_widget, fullscreen_video, (not fullscreen_video) or video_frame_drawn)
+                        
+                    if first_pass:
+                        scene_lists.set_times(self.interact_time)
+                        for k, v in self.transition_time.items():
+                            if v is None:
+                                self.transition_time[k] = self.interact_time
+
+                    renpy.config.frames += 1
+
+                    # If profiling is enabled, report the profile time.
+                    if renpy.config.profile :
+                        new_time = get_time()
+
+                        if new_time - self.profile_time > .015:
+                            print("Profile: Redraw took %f seconds." % (new_time - self.frame_time))
+                            print("Profile: %f seconds to complete event." % (new_time - self.profile_time))
+                        
+                    if first_pass and self.last_event:
+                        x, y = renpy.display.draw.get_mouse_pos()
+                        renpy.display.focus.mouse_handler(self.last_event, x, y, default=False)
+
+                    needs_redraw = False
+                    first_pass = False
+
+                    pygame.time.set_timer(REDRAW, 0)
+                    pygame.event.clear([REDRAW])
+                    old_redraw_time = None
+
+                # Draw the mouse, if it needs drawing.
+                renpy.display.draw.update_mouse()
+                    
+                # See if we want to restart the interaction entirely.
+                if self.restart_interaction:                    
+                    return True, None
+
+                # Determine if we need a redraw. (We want to run these 
+                # functions, so we put them first to prevent short-circuiting.) 
+
+                if renpy.display.video.frequent():
+                    needs_redraw = True
+                    video_frame_drawn = True
+                
+                needs_redraw = renpy.display.video.frequent() or needs_redraw
+                needs_redraw = renpy.display.render.process_redraws() or needs_redraw
+
+                # How many seconds until we timeout.
+                timeout_in = 3600
+
+                # Handle the redraw timer.
+                redraw_time = renpy.display.render.redraw_time()
+
+                if (redraw_time is not None) and not needs_redraw:
+                    if redraw_time != old_redraw_time:
+                        time_left = redraw_time - get_time()
+                        time_left = min(time_left, 3600)
+                        redraw_in = time_left
+                        
+                        if time_left <= 0:
+                            try:
+                                pygame.event.post(self.redraw_event)
+                            except:
+                                pass
+                            pygame.time.set_timer(REDRAW, 0)
+                        else:
+                            pygame.time.set_timer(REDRAW, max(int(time_left * 1000), 1))
+                        
+                        old_redraw_time = redraw_time
+                else:
+                    redraw_in = 3600
+                    pygame.time.set_timer(REDRAW, 0)
+
+                # Handle the timeout timer.
+                if not self.timeout_time:
+                    pygame.time.set_timer(TIMEEVENT, 0)
+                else:
+                    time_left = self.timeout_time - get_time() 
+                    time_left = min(time_left, 3600)
+                    timeout_in = time_left
+                    
+                    if time_left <= 0:
+                        self.timeout_time = None
+                        pygame.time.set_timer(TIMEEVENT, 0)
+                        self.post_time_event()
+                    elif self.timeout_time != old_timeout_time:
+                        # Always set to at least 1ms.
+                        pygame.time.set_timer(TIMEEVENT, int(time_left * 1000 + 1))
+                        old_timeout_time = self.timeout_time
+
+                # Predict images, if we haven't done so already.
+                while prediction_coroutine is not None:
+                    
+                    # Can we do expensive prediction?
+                    expensive_predict = not (needs_redraw or self.event_peek() or renpy.audio.music.is_playing("movie"))
+                    
+                    result = prediction_coroutine.send(expensive_predict)
+
+                    if not result:
+                        prediction_coroutine = None
+                        break
+
+                    if not expensive_predict:
+                        break
+
+                # If we need to redraw again, do it if we don't have an
+                # event going on.
+                if needs_redraw and not self.event_peek():
+                    if renpy.config.profile:
+                        self.profile_time = get_time()
+                    continue
+
+                # Handle autosaving, as necessary.
+                if not did_autosave and not needs_redraw and not self.event_peek() and redraw_in > .25 and timeout_in > .25:
+                    renpy.loadsave.autosave()
+                    did_autosave = True
+
+                if needs_redraw or renpy.display.video.playing():
+                    ev = self.event_poll()
+                else:
+                    ev = self.event_wait()
+                    
+                if ev.type == pygame.NOEVENT:
+                    continue
+
+                if renpy.config.profile:
+                    self.profile_time = get_time()
+                
+                # Try to merge an TIMEEVENT with other timeevents.
+                if ev.type == TIMEEVENT:
+                    old_timeout_time = None
+                    pygame.event.clear([TIMEEVENT])
+
+                # On Android, where we have multiple mouse buttons, we can
+                # merge a mouse down and mouse up event with its successor. This 
+                # prevents us from getting overwhelmed with too many events on 
+                # a multitouch screen.
+                if android and (ev.type == pygame.MOUSEBUTTONDOWN or ev.type == pygame.MOUSEBUTTONUP):
+                    pygame.event.clear(ev.type)
+                        
+                # Handle redraw timeouts.
+                if ev.type == REDRAW:
+                    pygame.event.clear([REDRAW])
+                    old_redraw_time = None
+                    continue
+
+                # Handle periodic events. This includes updating the mouse timers (and through the loop,
+                # the mouse itself), and the audio system periodic calls.
+                if ev.type == PERIODIC:
+                    events = 1 + len(pygame.event.get([PERIODIC]))
+                    self.ticks += events
+
+                    if renpy.config.periodic_callback:
+                        renpy.config.periodic_callback()
+
+                    renpy.audio.audio.periodic()
+                    continue
+
+                # This can set the event to None, to ignore it.
+                ev = renpy.display.joystick.event(ev)
+                if not ev:
+                    continue
+
+                # Handle skipping.
+                renpy.display.behavior.skipping(ev)
+                
+                # Handle quit specially for now.
+                if ev.type == pygame.QUIT:
+                    self.quit_event()
+                    continue
+                    
+                # Handle videoresize.
+                if ev.type == pygame.VIDEORESIZE:
+                    evs = pygame.event.get([pygame.VIDEORESIZE])
+                    if len(evs):
+                        ev = evs[-1]
+
+                    if self.last_resize != ev.size:
+                        self.last_resize = ev.size
+                        self.set_mode((ev.w, ev.h))
+
+                    continue
+
+                if ev.type == pygame.MOUSEMOTION or \
+                        ev.type == pygame.MOUSEBUTTONDOWN or \
+                        ev.type == pygame.MOUSEBUTTONUP:
+        
+                    self.mouse_event_time = renpy.display.core.get_time()
+                
+                # Merge mousemotion events.
+                if ev.type == pygame.MOUSEMOTION:
+                    evs = pygame.event.get([pygame.MOUSEMOTION])
+                    if len(evs):
+                        ev = evs[-1]
+
+                    if renpy.windows:
+                        self.focused = True
+                        
+                # Handle focus notifications.
+                if ev.type == pygame.ACTIVEEVENT:
+                    if ev.state & 1:
+                        self.focused = ev.gain
+
+                    if ev.state & 4:                            
+                        if ev.gain:
+                            self.restored()
+                        else:
+                            self.iconified()
+                
+                    pygame.key.set_mods(0)
+
+                # This returns the event location. It also updates the
+                # mouse state as necessary.
+                x, y = renpy.display.draw.mouse_event(ev)
+
+                if not self.focused:
+                    x = -1
+                    y = -1
+                
+                self.event_time = end_time = get_time()
+
+                try:
+
+                    # Handle the event normally.
+                    rv = renpy.display.focus.mouse_handler(ev, x, y)
+
+
+                    if rv is None:
+                        rv = root_widget.event(ev, x, y, 0)
+
+                    if rv is None:
+                        rv = renpy.display.focus.key_handler(ev)
+
+                    if rv is not None:
+                        break
+                    
+                    # Handle displayable inspector.
+                    if renpy.config.inspector and renpy.display.behavior.inspector(ev):
+                        l = self.surftree.main_displayables_at_point(x, y, renpy.config.transient_layers + renpy.config.context_clear_layers + renpy.config.overlay_layers)
+                        renpy.game.invoke_in_new_context(renpy.config.inspector, l)
+            
+                except IgnoreEvent:
+                    # An ignored event can change the timeout. So we want to
+                    # process an TIMEEVENT to ensure that the timeout is
+                    # set correctly.
+                    self.post_time_event()
+                        
+                        
+                # Check again after handling the event.
+                needs_redraw |= renpy.display.render.process_redraws()
+
+                if self.restart_interaction:
+                    return True, None
+
+            # If we were trans-paused and rv is true, suppress
+            # transitions up to the next interaction.
+            if trans_pause and rv:
+                self.suppress_transition = True
+                
+            # But wait, there's more! The finally block runs some cleanup
+            # after this.
+            return False, rv
+
+        except EndInteraction as e:
+            return False, e.value
+
+        finally:
+
+            renpy.exports.say_attributes = None
+
+            # Clean out the overlay layers.
+            for i in renpy.config.overlay_layers:
+                scene_lists.clear(i)
+
+            # Stop ongoing preloading.
+            renpy.display.im.cache.end_tick()
+                
+            # We no longer disable periodic between interactions.
+            # pygame.time.set_timer(PERIODIC, 0)
+
+            pygame.time.set_timer(TIMEEVENT, 0)
+            pygame.time.set_timer(REDRAW, 0)
+
+            renpy.game.context().runtime += end_time - start_time
+
+            # Restart the old interaction, which also causes a
+            # redraw if needed.
+            self.restart_interaction = True
+
+            # print "It took", frames, "frames."
+
+    def timeout(self, offset):
+        if offset < 0:
+            return
+
+        if self.timeout_time:
+            self.timeout_time = min(self.event_time + offset, self.timeout_time)
+        else:
+            self.timeout_time = self.event_time + offset
+
diff --git a/unrpyc/renpy/display/dragdrop.py b/unrpyc/renpy/display/dragdrop.py
new file mode 100644
index 0000000..0b99e63
--- /dev/null
+++ b/unrpyc/renpy/display/dragdrop.py
@@ -0,0 +1,731 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# TODO: Use overlap (rather than simple pointer location) to determine
+# drag and drop.
+
+import renpy.display
+from renpy.display.render import render, Render, redraw
+from renpy.display.core import absolute
+from renpy.display.behavior import map_event, run
+
+import pygame
+
+def default_drag_group():
+    """
+    Gets the default drag group. If it doesn't exist yet, creates it.
+    """
+
+    sls = renpy.game.context().scene_lists
+
+    rv = sls.drag_group
+
+    if rv is None:
+        rv = DragGroup()
+        sls.drag_group = rv
+
+    return rv
+
+def default_drag_joined(drag):
+    return [ (drag, 0, 0) ]
+
+class Drag(renpy.display.core.Displayable, renpy.python.RevertableObject):
+    """
+    :doc: drag_drop class
+    :args: (d=None, drag_name=None, draggable=True, droppable=True, drag_raise=True, dragged=None, dropped=None, drag_handle=(0.0, 0.0, 1.0, 1.0), drag_joined=..., clicked=None, hovered=None, unhovered=None, **properties)
+  
+    A displayable that represents an object that can be dragged around
+    its enclosing area. A Drag can also represent an area that
+    other Drags can be dropped on.
+
+    A Drag can be moved around inside is parent. Generally, its parent
+    should be either a :func:`Fixed` or :class:`DragGroup`.
+    
+    A Drag has one child. The child's state reflects the status
+    of the drag and drop operation:
+
+    * ``selected_hover`` - when it is being dragged.
+    * ``selected_idle`` - when it can be dropped on.
+    * ``hover`` - when the draggable will be dragged when the mouse is
+      clicked.
+    * ``idle`` - otherwise. 
+
+    The drag handle is a rectangle inside the child. The mouse must be over
+    a non-transparent pixel inside the drag handle for dragging or clicking
+    to occur. 
+    
+    A newly-created draggable is added to the default DragGroup. A draggable
+    can only be in a single DragGroup - if it's added to a second group,
+    it's removed from the first.
+
+    When a Drag is first rendered, if it's position cannot be determined
+    from the DragGroup it is in, the position of its upper-left corner
+    is computed using the standard layout algorithm. Once that position
+    
+    
+    `d`
+        If present, the child of this Drag. Drags use the child style
+        in preference to this, if it's not None.
+    
+    `drag_name`
+        If not None, the name of this draggable. This is available
+        as the `name` property of draggable objects. If a Drag
+        with the same name is or was in the DragGroup, the starting
+        position of this Drag is taken from that Draggable.
+
+    `draggable`
+        If true, the Drag can be dragged around the screen with
+        the mouse.
+
+    `droppable`
+        If true, other Drags can be dropped on this Drag.
+
+    `drag_raise`
+        If true, this Drag is raised to the top when it is dragged. If
+        it is joined to other Drags, all joined drags are raised. 
+
+    `dragged`
+        A callback (or list of callbacks) that is called when the Drag
+        has been dragged. It is called with two arguments. The first is
+        a list of Drags that are being dragged. The second is either
+        a Drag that is being dropped onto, or None of a drop did not
+        occur. If the callback returns a value other than None, that
+        value is returned as the result of the interaction.
+
+    `dropped`
+        A callback (or list of callbacks) that is called when this Drag
+        is dropped onto. It is called with two arguments. The first
+        is the Drag being dropped onto. The second is a list of Drags that
+        are being dragged.  If the callback returns a value other than None,
+        that value is returned as the result of the interaction.
+
+        When a dragged and dropped callback are triggered for the same
+        event, the dropped callback is only called if dragged returns
+        None.
+        
+    `clicked`       
+        A callback this is called, with no arguments, when the Drag is
+        clicked without being moved. A droppable can also be focused
+        and clicked.  If the callback returns a value othe than None,
+        that value is returned as the result of the interaction.
+        
+    `drag_handle`
+        A (x, y, width, height) tuple, giving the position of the drag
+        handle within the child. In this tuple, integers are considered
+        to be a literal number of pixels, while floats are relative to
+        the size of the child. 
+
+    `drag_joined`
+        This is called with the current Drag as an argument. It's
+        expected to return a list of [ (drag, x, y) ] tuples, giving
+        the draggables to drag as a unit. `x` and `y` are the offsets
+        of the drags relative to each other, they are not relative
+        to the corner of this drag.
+
+    Except for `d`, all of the parameters are available as fields (with
+    the same name) on the Drag object. In addition, after the drag has
+    been rendered, the following fields become available:
+
+    `x`, `y`
+         The position of the Drag relative to its parent, in pixels.
+
+    `w`, `h`
+         The width and height of the Drag's child, in pixels.
+        """
+
+    def __init__(self,
+                 d=None,
+                 drag_name=None,
+                 draggable=True,
+                 droppable=True,
+                 drag_raise=True,
+                 dragged=None,
+                 dropped=None,
+                 drag_handle=(0.0, 0.0, 1.0, 1.0),
+                 drag_joined=default_drag_joined,
+                 clicked=None,
+                 hovered=None,
+                 unhovered=None,
+                 replaces=None,
+                 **properties):
+
+        super(Drag, self).__init__(self, **properties)
+
+        self.drag_name = drag_name
+        self.draggable = draggable
+        self.droppable = droppable
+        self.drag_raise = drag_raise
+        self.dragged = dragged 
+        self.dropped = dropped
+        self.drag_handle = drag_handle
+        self.drag_joined = drag_joined
+        self.clicked = clicked
+        self.hovered = hovered
+        self.unhovered = unhovered
+
+        self.child = None
+        
+        # Add us to a drag group on creation.
+        if drag_name:
+            self.drag_group = default_drag_group()
+            
+        # The current x and y coordinates of this displayable.
+        self.x = None
+        self.y = None
+
+        # The width and height of the child.
+        self.w = None
+        self.h = None
+
+        # The width and height of our parent.
+        self.parent_width = None
+        self.parent_height = None
+        
+        # The target x and y coordinates of this displayable. (The
+        # coordinates that we're snapping to.)
+        self.target_x = None
+        self.target_y = None
+
+        # The offset from the location of the mouse to the "grab point",
+        # which is where the things that are being moved are offset from.
+        self.grab_x = None
+        self.grab_y = None
+
+        # x and y from the last time we rendered.
+        self.last_x = None
+        self.last_y = None
+
+        # The abs_x and abs_y from when we started the grab.
+        self.start_x = 0
+        self.start_y = 0
+        
+        # The last time we were shown, using the animation timebases.
+        self.at = 0
+
+        # The (animation timebase) time at which we should reach
+        # the target coordinates.
+        self.target_at = 0
+
+        # The displayable we were last dropping on.
+        self.last_drop = None
+
+        # Did we move over the course of this drag?
+        self.drag_moved = False
+        
+        if replaces is not None:
+            self.x = replaces.x
+            self.y = replaces.y
+            self.at = replaces.at
+            self.target_x = replaces.target_x
+            self.target_y = replaces.target_y
+            self.target_at = replaces.target_at
+        
+        if d is not None:
+            self.add(d)
+
+            
+    def snap(self, x, y, delay=0):
+        """
+        :doc: drag_drop method
+
+        Changes the position of the drag. If the drag is not showing,
+        then the position change is instantaneous. Otherwise, the
+        position change takes `delay` seconds, and is animated as a
+        linear move.
+        """
+
+        self.target_x = x
+        self.target_y = y
+
+        if self.x is not None:
+            self.target_at = self.at + delay
+        else:
+            self.target_at = self.at
+            self.x = x
+            self.y = y
+            
+        redraw(self, 0)
+        
+    def set_style_prefix(self, prefix, root):
+        super(Drag, self).set_style_prefix(prefix, root)
+
+        if self.child is not None:
+            self.child.set_style_prefix(prefix, False)
+            
+    def add(self, d):
+        if self.child is not None:
+            raise Exception("Drag expects either zero or one children.")
+
+        self.child = renpy.easy.displayable(d)
+
+    def set_child(self, d):
+        """
+        :doc: drag_drop method
+
+        Changes the child of this drag to `d`.
+        """
+
+        d.per_interact()
+        self.child = renpy.easy.displayable(d)
+
+    def top(self):
+        """
+        :doc: drag_drop method
+
+        Raises this displayable to the top of its drag_group.
+        """
+        
+        if self.drag_group is not None:
+            self.drag_group.raise_children([ self ])
+        
+    def visit(self):
+        return [ self.child ]
+
+    def focus(self, default=False):
+        super(Drag, self).focus(default)
+
+        rv = None
+
+        if not default:
+            rv = run(self.hovered)
+        
+        return rv
+
+    def unfocus(self, default=False):
+        super(Drag, self).unfocus(default)
+
+        if not default:
+            run(self.unhovered)
+    
+    def render(self, width, height, st, at):
+
+        child = self.style.child
+        if child is None:
+            child = self.child            
+        
+        self.parent_width = width
+        self.parent_height = height
+        
+        cr = render(child, width, height, st, at)
+        cw, ch = cr.get_size()
+        
+        rv = Render(cw, ch)
+        rv.blit(cr, (0, 0))
+
+        self.w = cw
+        self.h = ch
+
+        # If we don't have a position, then look for it in a drag group.
+        if (self.x is None) and (self.drag_group is not None) and (self.drag_name is not None):
+            if self.drag_name in self.drag_group.positions:                
+                self.x, self.y = self.drag_group.positions[self.drag_name]
+        
+        # If we don't have a position, run the placement code and use
+        # that to compute our placement.
+        if self.x is None:
+            self.x, self.y = self.place(None, 0, 0, width, height, rv)
+            self.x = int(self.x)
+            self.y = int(self.y)
+
+        if self.target_x is None:
+            self.target_x = self.x
+            self.target_y = self.y
+            self.target_at = at
+
+        # Determine if we need to do the snap animation.
+        if at >= self.target_at:
+            self.x = self.target_x
+            self.y = self.target_y
+        else:
+            done = (at - self.at) / (self.target_at - self.at)
+            self.x = absolute(self.x + done * (self.target_x - self.x))
+            self.y = absolute(self.y + done * (self.target_y - self.y))
+            redraw(self, 0)
+
+        if self.draggable or self.clicked is not None:
+            
+            fx, fy, fw, fh = self.drag_handle
+
+            if isinstance(fx, float):
+                fx = int(fx * cw)
+
+            if isinstance(fy, float):
+                fy = int(fy * ch)
+
+            if isinstance(fw, float):
+                fw = int(fw * cw)
+
+            if isinstance(fh, float):
+                fh = int(fh * ch)
+
+            rv.add_focus(self, None, fx, fy, fw, fh, fx, fy, cr.subsurface((fx, fy, fw, fh)))
+
+        self.last_x = self.x
+        self.last_y = self.y
+        self.at = at
+        
+        return rv
+
+    def event(self, ev, x, y, st):
+
+        if not self.is_focused():
+            return self.child.event(ev, x, y, st)
+
+        # if not self.draggable:
+        #    return self.child.event(ev, x, y, st)
+                
+        # Mouse, in parent-relative coordinates.
+        par_x = self.last_x + x
+        par_y = self.last_y + y
+        
+        grabbed = (renpy.display.focus.get_grab() is self)
+
+        if grabbed:
+            joined_offsets = self.drag_joined(self)
+            joined = [ i[0] for i in joined_offsets ]
+
+        elif self.draggable and map_event(ev, "drag_activate"):
+
+            joined_offsets = self.drag_joined(self)
+            joined = [ i[0] for i in joined_offsets ]
+            
+            if not joined:
+                raise renpy.display.core.IgnoreEvent()
+
+            renpy.display.focus.set_grab(self)
+
+            self.grab_x = x
+            self.grab_y = y
+
+            # If we're not the only thing we're joined with, we
+            # might need to adjust our grab point.
+            for i, xo, yo in joined_offsets:
+                if i is self:
+                    self.grab_x += xo
+                    self.grab_y += yo
+                    break
+                
+            self.drag_moved = False
+            self.start_x = par_x
+            self.start_y = par_y
+            
+            grabbed = True
+
+        # Handle clicking on droppables.
+        if not grabbed:
+            if self.clicked is not None and map_event(ev, "drag_deactivate"):
+                rv = run(self.clicked)
+                if rv is not None:
+                    return rv
+
+                raise renpy.display.core.IgnoreEvent()
+
+            return self.child.event(ev, x, y, st)
+
+        # Handle moves by moving things relative to the grab point.
+        if ev.type in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+
+            if not self.drag_moved and (self.start_x != par_x or self.start_y != par_y):
+                self.drag_moved = True
+
+                # We may not be in the drag_joined group.
+                self.set_style_prefix("idle_", True)
+
+                # Set the style.
+                for i in joined:
+                    i.set_style_prefix("selected_hover_", True)
+
+                # Raise the joined items.
+                if self.drag_raise and self.drag_group is not None:
+                    self.drag_group.raise_children(joined)
+
+            if self.drag_moved:
+                for i, xo, yo in joined_offsets:
+
+                    new_x = par_x - self.grab_x + xo
+                    new_y = par_y - self.grab_y + yo
+                    new_x = max(new_x, 0)
+                    new_x = min(new_x, i.parent_width - i.w)
+                    new_y = max(new_y, 0)
+                    new_y = min(new_y, i.parent_height - i.h)            
+
+                    if i.drag_group is not None and i.drag_name is not None:
+                        i.drag_group.positions[i.drag_name] = (new_x, new_y)
+
+                    i.x = new_x
+                    i.y = new_y
+                    i.target_x = new_x
+                    i.target_y = new_y
+                    i.target_at = self.at
+                    redraw(i, 0)
+
+        if (self.drag_group is not None) and self.drag_moved:
+            drop = self.drag_group.get_best_drop(joined)
+        else:
+            drop = None
+            
+        if drop is not self.last_drop:
+
+            if self.last_drop is not None:
+                self.last_drop.set_style_prefix("idle_", True)
+            
+            if drop is not None:
+                drop.set_style_prefix("selected_idle_", True)
+
+            self.last_drop = drop
+            
+        if map_event(ev, 'drag_deactivate'):
+            renpy.display.focus.set_grab(None)
+
+            if drop is not None:
+                drop.set_style_prefix("idle_", True)
+
+            for i in joined:
+                i.set_style_prefix("idle_", True)
+                
+            self.set_style_prefix("hover_", True)
+
+            self.grab_x = None
+            self.grab_y = None
+            self.last_drop = None
+
+            if self.drag_moved:
+
+                # Call the drag callback.
+                drag = joined[0]            
+                if drag.dragged is not None:
+                    rv = run(drag.dragged, joined, drop)
+                    if rv is not None:
+                        return rv
+
+                # Call the drop callback.
+                if drop is not None and drop.dropped is not None:
+                    rv = run(drop.dropped, drop, joined)
+                    if rv is not None:
+                        return rv
+
+            else:
+                    
+                # Call the clicked callback.
+                if self.clicked:
+                    rv = run(self.clicked)
+                    if rv is not None:
+                        return rv
+
+        raise renpy.display.core.IgnoreEvent()
+
+
+    def get_placement(self):
+
+        if self.x is not None:
+            return self.x, self.y, 0, 0, 0, 0, True
+        else:
+            return super(Drag, self).get_placement()
+
+    def per_interact(self):
+        self.set_style_prefix("idle_", True)
+        super(Drag, self).per_interact()
+                              
+        
+class DragGroup(renpy.display.layout.MultiBox):
+    """
+    :doc: drag_drop class
+
+    Represents a group of Drags. A Drag is limited to the boundary of
+    its DragGroup. Dropping only works between Drags that are in the
+    same DragGroup. Drags may only be raised when they are inside a
+    DragGroup.
+
+    A DragGroup is laid out like a :func:`Fixed`.
+    
+    All positional parameters to the DragGroup constructor should be
+    Drags, that are added to the DragGroup.
+    """
+
+    _list_type = renpy.python.RevertableList
+    
+    def __init__(self, *children, **properties):
+        properties.setdefault("style", "fixed")
+        properties.setdefault("layout", "fixed")
+
+        replaces = properties.pop("replaces", None)
+        
+        super(DragGroup, self).__init__(**properties)
+
+        if replaces is not None:
+            self.positions = renpy.python.RevertableDict(replaces.positions)
+            self.sensitive = replaces.sensitive
+        else:
+            self.positions = renpy.python.RevertableDict()
+            self.sensitive = True
+            
+        for i in children:
+            self.add(i)
+            
+
+    def add(self, child):
+        """
+        :doc: drag_drop method
+
+        Adds `child`, which must be a Drag, to this DragGroup.
+        """
+                
+        if not isinstance(child, Drag):
+            raise Exception("Only drags can be added to a drag group.")
+
+        child.drag_group = self
+        super(DragGroup, self).add(child)
+        
+    def remove(self, child):
+        """
+        :doc: drag_drop method
+
+        Removes `child` from this DragGroup.
+        """
+
+        
+        if not isinstance(child, Drag):
+            raise Exception("Only drags can be removed from a drag group.")
+        
+        child.x = None
+        super(DragGroup, self).remove(child)
+            
+        
+    def event(self, ev, x, y, st):
+
+        if not self.sensitive:
+            return None
+
+        return super(DragGroup, self).event(ev, x, y, st)
+        
+    def raise_children(self, l):
+        """
+        Raises the children in `l` to the top of this drag_group, using the
+        order given in l for those children.
+        """
+
+        s = set(l)
+
+        offset_map = { }
+
+        children = [ ]
+        offsets = [ ]
+        
+        for i, c in enumerate(self.children):
+            if i < len(self.offsets):
+                o = self.offsets[i]
+            else:
+                o = (0, 0)
+            
+            if c not in s:
+                children.append(c)
+                offsets.append(o)
+            else:
+                offset_map[c] = o
+
+        for c in l:
+            if c in offset_map:            
+                children.append(c)
+                offsets.append(offset_map[c])
+
+        self.children = self._list_type(children)
+        self.offsets = self._list_type(offsets)
+
+                    
+    def get_best_drop(self, joined):
+        """
+        Returns the droppable that the members of joined overlap the most.
+        """
+
+        max_overlap = 0
+        rv = 0
+        
+        joined_set = set(joined)
+
+        for d in joined:
+
+            r1 = (d.x, d.y, d.w, d.h)
+
+            for c in self.children:
+                if c in joined_set:
+                    continue
+
+                if not c.droppable:
+                    continue
+                
+                r2 = (c.x, c.y, c.w, c.h)
+
+                overlap = rect_overlap_area(r1, r2)
+                
+                if overlap >= max_overlap:
+                    rv = c
+                    max_overlap = overlap
+
+        if max_overlap <= 0:
+            return None
+        else:
+            return rv
+
+    def get_children(self):
+        """
+        Returns a list of Drags that are the children of
+        this DragGroup.
+        """
+        
+        return renpy.python.RevertableList(self.children)
+
+    def get_child_by_name(self, name):
+        """
+        :doc: drag_drop method
+        
+        Returns the first child of this DragGroup that has a drag_name
+        of name.
+        """
+
+        for i in self.children:
+            if i.drag_name == name:
+                return i
+
+        return None
+        
+            
+def rect_overlap_area(r1, r2):
+    """
+    Returns the number of pixels by which rectangles r1 and r2 overlap.
+    """
+    
+    x1, y1, w1, h1 = r1
+    x2, y2, w2, h2 = r2
+    
+    maxleft = max(x1, x2)
+    minright = min(x1 + w1, x2 + w2)
+    maxtop = max(y1, y2)
+    minbottom = min(y1 + h1, y2 + h2)
+    
+    if minright < maxleft:
+        return 0
+
+    if minbottom < maxtop:
+        return 0
+
+    return (minright - maxleft) * (minbottom - maxtop)
+   
+            
diff --git a/unrpyc/renpy/display/error.py b/unrpyc/renpy/display/error.py
new file mode 100644
index 0000000..a8f7e1b
--- /dev/null
+++ b/unrpyc/renpy/display/error.py
@@ -0,0 +1,159 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains code to handle GUI-based error reporting.
+
+import renpy.display
+import os
+
+error_handled = False
+
+##############################################################################
+# Initialized approach.
+
+def call_exception_screen(screen_name, **kwargs):
+    try:
+
+        old_quit = renpy.config.quit_action
+        renpy.config.quit_action = renpy.exports.quit
+
+        for i in renpy.config.layers:
+            renpy.game.context().scene_lists.clear(i)
+    
+        renpy.exports.show_screen(screen_name, _transient=True, **kwargs)
+        return renpy.ui.interact(mouse="screen", type="screen", suppress_overlay=True, suppress_underlay=True)
+
+    finally:
+        renpy.config.quit_action = old_quit
+
+def rollback_action():
+    renpy.exports.rollback(force=True)
+    
+def init_display():
+    """
+    The minimum amount of code required to init the display.
+    """
+
+    if not renpy.game.interface:
+        renpy.display.core.Interface()
+        renpy.loader.index_archives()
+        renpy.display.im.cache.init()
+        renpy.style.styles_built = True # The styles we use were built in renpy.main.
+
+    renpy.ui.reset()
+       
+def error_dump():
+    """
+    Handles dumps in the case where an error occurs.
+    """
+    
+    renpy.dump.dump(True)
+        
+def report_exception(short, full, traceback_fn):
+    """
+    Reports an exception to the user. Returns True if the exception should
+    be raised by the normal reporting mechanisms. Otherwise, should raise
+    the appropriate exception to cause a reload or quit or rollback.
+    """
+
+    global error_handled
+    error_handled = True
+
+    error_dump()
+
+    if renpy.game.args.command != "run": #@UndefinedVariable
+        return True
+
+    if "RENPY_SIMPLE_EXCEPTIONS" in os.environ:
+        return True
+       
+    if not renpy.exports.has_screen("_exception"):
+        return True
+    
+    try:
+        init_display()    
+    except:
+        return True
+    
+    if renpy.display.draw is None:
+        return True
+    
+    ignore_action = None
+    rollback_action = None
+    reload_action = None
+
+    try:    
+        if not renpy.game.context().init_phase:
+
+            if renpy.config.rollback_enabled:
+                rollback_action = renpy.display.error.rollback_action
+
+            reload_action = renpy.exports.curried_call_in_new_context("_save_reload_game")
+
+        if renpy.game.context(-1).next_node is not None:
+            ignore_action = renpy.ui.returns(False)
+    except:
+        pass     
+     
+    renpy.game.invoke_in_new_context(
+        call_exception_screen,
+        "_exception", 
+        short=short, full=full, 
+        rollback_action=rollback_action,
+        reload_action=reload_action,
+        ignore_action=ignore_action,
+        traceback_fn=traceback_fn,
+        )
+        
+
+def report_parse_errors(errors, error_fn):
+    """
+    Reports an exception to the user. Returns True if the exception should
+    be raised by the normal reporting mechanisms. Otherwise, should raise
+    the appropriate exception.
+    """
+
+    global error_handled
+    error_handled = True
+
+    error_dump()
+
+    if renpy.game.args.command != "run": #@UndefinedVariable
+        return True
+
+    if "RENPY_SIMPLE_EXCEPTIONS" in os.environ:
+        return True
+       
+    if not renpy.exports.has_screen("_parse_errors"):
+        return True
+    
+    init_display()    
+          
+    reload_action = renpy.exports.utter_restart
+     
+    renpy.game.invoke_in_new_context(
+        call_exception_screen,
+        "_parse_errors",
+        reload_action=reload_action,
+        errors=errors,
+        error_fn = error_fn,
+        )
+
diff --git a/unrpyc/renpy/display/focus.py b/unrpyc/renpy/display/focus.py
new file mode 100644
index 0000000..521fe22
--- /dev/null
+++ b/unrpyc/renpy/display/focus.py
@@ -0,0 +1,449 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains code to manage focus on the display.
+
+import pygame
+import renpy.display
+
+class Focus(object):
+
+    def __init__(self, widget, arg, x, y, w, h):
+
+        self.widget = widget
+        self.arg = arg
+        self.x = x
+        self.y = y
+        self.w = w
+        self.h = h
+
+    def copy(self):
+        return Focus(
+            self.widget,
+            self.arg,
+            self.x,
+            self.y,
+            self.w,
+            self.h)
+
+    def __repr__(self):
+        return "<Focus: %r %r (%r, %r, %r, %r)>" % (
+            self.widget,
+            self.arg,
+            self.x,
+            self.y,
+            self.w,
+            self.h)
+            
+    
+# The current focus argument.
+argument = None
+    
+# The widget currently grabbing the input, if any.
+grab = None
+
+# The default focus for the current screen.
+default_focus = None
+
+# Sets the currently focused widget.
+def set_focused(widget, arg):
+    global argument
+    argument = arg
+    renpy.game.context().scene_lists.focused = widget
+
+# Gets the currently focused widget.
+def get_focused():
+    return renpy.game.context().scene_lists.focused
+
+# Get the mouse cursor for the focused widget.
+def get_mouse():
+    focused = get_focused()
+    if focused is None:
+        return None
+    else:
+        return focused.style.mouse
+    
+def set_grab(widget):
+    global grab
+    grab = widget
+
+def get_grab():
+    return grab
+    
+# The current list of focuses that we know about.
+focus_list = [ ]
+
+# This takes in a focus list from the rendering system.
+def take_focuses():
+    global focus_list
+    focus_list = [ ]
+
+    renpy.display.render.take_focuses(focus_list)
+
+    global default_focus
+    default_focus = None
+    
+    for f in focus_list:
+        if f.x is None:
+            default_focus = f
+
+def focus_coordinates():
+    """
+    :doc: other
+
+    This attempts to find the coordinates of the currently-focused
+    displayable. If it can, it will return them as a (x, y, w, h)
+    tuple. If not, it will return a (None, None, None, None) tuple.
+    """
+
+    current = get_focused()
+    
+    for i in focus_list:
+        if i.widget == current and i.arg == argument:
+            return i.x, i.y, i.w, i.h
+
+    return None, None, None, None
+    
+
+# This is called before each interaction. It's purpose is to choose
+# the widget that is focused, and to mark it as focused and all of
+# the other widgets as unfocused.
+
+# The new grab widget. (The one that replaced the old grab widget at the start
+# of the interaction.)
+new_grab = None
+
+def before_interact(roots):
+    
+    global new_grab
+    global grab
+
+    # a list of focusable, name tuples.
+    fwn = [ ]
+
+    def callback(f, n):
+        fwn.append((f, n))
+
+    for root in roots:
+        root.find_focusable(callback, None)
+
+    # Assign a full name to each focusable.
+
+    namecount = { }
+
+    for f, n in fwn:
+        serial = namecount.get(n, 0)
+        namecount[n] = serial + 1
+        
+        f.full_focus_name = n, serial
+
+    # If there's something with the same full name as the current widget,
+    # it becomes the new current widget.
+
+    current = get_focused()
+
+    if current is not None:
+        current_name = current.full_focus_name
+
+        for f, n in fwn:
+            if f.full_focus_name == current_name:
+                current = f
+                set_focused(f, None)
+                break
+        else:
+            current = None
+            
+    # Otherwise, focus the default widget, or nothing.
+    if current is None:
+
+        for f, n in fwn:
+            if f.default:
+                current = f
+                set_focused(f, None)
+                break
+        else:        
+            set_focused(None, None)
+
+    # Finally, mark the current widget as the focused widget, and
+    # all other widgets as unfocused.
+    for f, n in fwn:
+        if f is not current:
+            f.unfocus(default=True)
+
+    if current:
+        current.focus(default=True)
+
+    grab = new_grab
+    new_grab = None
+
+# This changes the focus to be the widget contained inside the new
+# focus object.
+def change_focus(newfocus, default=False):
+    rv = None
+    
+    if grab:
+        return
+
+    if newfocus is None:
+        widget = None
+    else:
+        widget = newfocus.widget
+
+    current = get_focused()
+
+    # Nothing to do.
+    if current is widget and (newfocus is None or newfocus.arg == argument):
+        return rv
+
+    if current is not None:
+        current.unfocus(default=default)
+
+    current = widget
+
+    if newfocus is not None:
+        arg = newfocus.arg
+    else:
+        arg = None
+        
+    set_focused(current, arg)
+
+    if widget is not None:
+        rv = widget.focus(default=default)
+
+    return rv
+    
+# This handles mouse events, to see if they change the focus.
+def mouse_handler(ev, x, y, default=False):
+
+    if ev.type not in (pygame.MOUSEMOTION, pygame.MOUSEBUTTONUP, pygame.MOUSEBUTTONDOWN):
+        return
+    
+    new_focus = renpy.display.render.focus_at_point(x, y)
+    
+    if new_focus is None:
+        new_focus = default_focus
+
+    return change_focus(new_focus, default=default)
+
+
+# This focuses an extreme widget, which is one of the widgets that's
+# at an edge. To do this, we multiply the x, y, width, and height by
+# the supplied multiplers, add them all up, and take the focus with
+# the largest value.
+def focus_extreme(xmul, ymul, wmul, hmul):
+
+    max_focus = None
+    max_score = -(65536**2)
+        
+    for f in focus_list:
+
+        if not f.x:
+            continue
+
+        score = (f.x * xmul +
+                 f.y * ymul +
+                 f.w * wmul +
+                 f.h * hmul)
+
+        if score > max_score:
+            max_score = score
+            max_focus = f
+
+    if max_focus:
+        return change_focus(max_focus)
+        
+
+# This calculates the distance between two points, applying
+# the given fudge factors. The distance is left squared.
+def points_dist(x0, y0, x1, y1, xfudge, yfudge):
+    return (( x0 - x1 ) * xfudge ) ** 2 + \
+           (( y0 - y1 ) * yfudge ) ** 2
+    
+
+# This computes the distance between two horizontal lines. (So the
+# distance is either vertical, or has a vertical component to it.)
+#
+# The distance is left squared.
+def horiz_line_dist(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1):
+
+    # The lines overlap in x.
+    if bx0 <= ax0 <= ax1 <= bx1 or \
+       ax0 <= bx0 <= bx1 <= ax1 or \
+       ax0 <= bx0 <= ax1 <= bx1 or \
+       bx0 <= ax0 <= bx1 <= ax1:
+        return (ay0 - by0) ** 2
+
+    # The right end of a is to the left of the left end of b.
+    if ax0 <= ax1 <= bx0 <= bx1:
+        return points_dist(ax1, ay1, bx0, by0, renpy.config.focus_crossrange_penalty, 1.0)
+
+    if bx0 <= bx1 <= ax0 <= ax1:
+        return points_dist(ax0, ay0, bx1, by1, renpy.config.focus_crossrange_penalty, 1.0)
+
+    assert False
+
+# This computes the distance between two vertical lines. (So the
+# distance is either hortizontal, or has a horizontal component to it.)
+#
+# The distance is left squared.
+def verti_line_dist(ax0, ay0, ax1, ay1, bx0, by0, bx1, by1):
+
+    # The lines overlap in x.
+    if by0 <= ay0 <= ay1 <= by1 or \
+       ay0 <= by0 <= by1 <= ay1 or \
+       ay0 <= by0 <= ay1 <= by1 or \
+       by0 <= ay0 <= by1 <= ay1:
+        return (ax0 - bx0) ** 2
+
+    # The right end of a is to the left of the left end of b.
+    if ay0 <= ay1 <= by0 <= by1:
+        return points_dist(ax1, ay1, bx0, by0, 1.0, renpy.config.focus_crossrange_penalty)
+
+    if by0 <= by1 <= ay0 <= ay1:
+        return points_dist(ax0, ay0, bx1, by1, 1.0, renpy.config.focus_crossrange_penalty)
+
+    assert False
+
+
+
+# This focuses the widget that is nearest to the current widget. To
+# determine nearest, we compute points on the widgets using the
+# {from,to}_{x,y}off values. We pick the nearest, applying a fudge
+# multiplier to the distances in each direction, that satisfies
+# the condition (which is given a Focus object to evaluate).
+#
+# If no focus can be found matching the above, we look for one
+# with an x of None, and make that the focus. Otherwise, we do
+# nothing.
+#
+# If no widget is focused, we pick one and focus it.
+# 
+# If the current widget has an x of None, we pass things off to
+# focus_extreme to deal with.
+def focus_nearest(from_x0, from_y0, from_x1, from_y1,
+                  to_x0, to_y0, to_x1, to_y1, 
+                  line_dist,
+                  condition,
+                  xmul, ymul, wmul, hmul):
+
+    if not focus_list:
+        return
+
+    # No widget focused.
+    current = get_focused()
+
+    if not current:
+        change_focus(focus_list[0])
+        return
+
+    # Find the current focus.
+    for f in focus_list:
+        if f.widget is current and f.arg == argument:
+            from_focus = f
+            break
+    else:
+        # If we can't pick something.
+        change_focus(focus_list[0])
+        return
+
+    # If placeless, focus_extreme.
+    if from_focus.x is None:
+        focus_extreme(xmul, ymul, wmul, hmul)
+        return
+
+    fx0 = from_focus.x + from_focus.w * from_x0
+    fy0 = from_focus.y + from_focus.h * from_y0
+    fx1 = from_focus.x + from_focus.w * from_x1
+    fy1 = from_focus.y + from_focus.h * from_y1
+
+    placeless = None
+    new_focus = None
+
+    # a really big number.
+    new_focus_dist = (65536.0 * renpy.config.focus_crossrange_penalty) ** 2
+
+    for f in focus_list:
+
+        if f is from_focus:
+            continue
+
+        if f.x is None:
+            placeless = f
+            continue
+
+        if not condition(from_focus, f):
+            continue
+        
+        tx0 = f.x + f.w * to_x0
+        ty0 = f.y + f.h * to_y0
+        tx1 = f.x + f.w * to_x1
+        ty1 = f.y + f.h * to_y1
+
+        dist = line_dist(fx0, fy0, fx1, fy1,
+                         tx0, ty0, tx1, ty1)
+        
+        if dist < new_focus_dist:
+            new_focus = f
+            new_focus_dist = dist
+
+    # If we couldn't find anything, try the placeless focus.
+    new_focus = new_focus or placeless
+
+    # If we have something, switch to it.
+    if new_focus:
+        return change_focus(new_focus)
+
+    # And, we're done.
+
+
+
+def key_handler(ev):
+
+    if renpy.display.behavior.map_event(ev, 'focus_right'):
+        return focus_nearest(0.9, 0.1, 0.9, 0.9,
+                             0.1, 0.1, 0.1, 0.9,
+                             verti_line_dist,                      
+                             lambda old, new : old.x + old.w <= new.x,
+                             -1, 0, 0, 0)
+        
+    if renpy.display.behavior.map_event(ev, 'focus_left'):
+        return focus_nearest(0.1, 0.1, 0.1, 0.9,
+                             0.9, 0.1, 0.9, 0.9,
+                             verti_line_dist,
+                             lambda old, new : new.x + new.w <= old.x,
+                             1, 0, 1, 0)
+
+    if renpy.display.behavior.map_event(ev, 'focus_up'):
+        return focus_nearest(0.1, 0.1, 0.9, 0.1,
+                             0.1, 0.9, 0.9, 0.9,
+                             horiz_line_dist,
+                             lambda old, new : new.y + new.h <= old.y,
+                             0, 1, 0, 1)
+
+    if renpy.display.behavior.map_event(ev, 'focus_down'):
+        return focus_nearest(0.1, 0.9, 0.9, 0.9,
+                             0.1, 0.1, 0.9, 0.1,
+                             horiz_line_dist,
+                             lambda old, new : old.y + old.h <= new.y,
+                             0, -1, 0, 0)
+
+        
+                                
diff --git a/unrpyc/renpy/display/im.py b/unrpyc/renpy/display/im.py
new file mode 100644
index 0000000..d90fb51
--- /dev/null
+++ b/unrpyc/renpy/display/im.py
@@ -0,0 +1,1562 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains the new image code, which includes provisions for
+# size-based caching and constructing images from operations (like
+# cropping and scaling).
+
+import renpy.display
+
+import math
+import zipfile
+import io
+import threading
+import time
+
+
+# This is an entry in the image cache.
+class CacheEntry(object):
+
+    def __init__(self, what, surf):
+
+        # The object that is being cached (which needs to be
+        # hashable and comparable).
+        self.what = what
+
+        # The pygame surface corresponding to the cached object.
+        self.surf = surf 
+
+        # The size of this image.
+        w, h = surf.get_size()
+        self.size = w * h
+
+        # The time when this cache entry was last used.
+        self.time = 0
+
+# This is the singleton image cache.
+class Cache(object):
+
+    def __init__(self):
+
+        # The current arbitrary time. (Increments by one for each
+        # interaction.)
+        self.time = 0
+
+        # A map from Image object to CacheEntry.
+        self.cache = { }
+
+        # A list of Image objects that we want to preload.
+        self.preloads = [ ]
+
+        # False if this is not the first preload in this tick.
+        self.first_preload_in_tick = True
+
+        # The total size of the current generation of images.
+        self.size_of_current_generation = 0
+
+        # The total size of everything in the cache.
+        self.total_cache_size = 0
+
+        # A lock that must be held when updating the cache.
+        self.lock = threading.Condition()
+
+        # A lock that mist be held to notify the preload thread.
+        self.preload_lock = threading.Condition()
+
+        # Is the preload_thread alive?
+        self.keep_preloading = True
+
+        # A map from image object to surface, only for objects that have
+        # been pinned into memory.
+        self.pin_cache = { }
+
+        # Images that we tried, and failed, to preload.
+        self.preload_blacklist = set()
+
+        # The size of the cache, in pixels.
+        self.cache_limit = 0
+
+        # The preload thread.
+        self.preload_thread = threading.Thread(target=self.preload_thread_main, name="preloader")
+        self.preload_thread.setDaemon(True)
+        self.preload_thread.start()
+
+        # Have we been added this tick?
+        self.added = set()
+
+        # A list of (time, filename, preload) tuples. This is updated when
+        # config.developer is True and an image is loaded. Preload is a 
+        # flag that is true if the image was loaded from the preload 
+        # thread. The log is limited to 100 entries, and the newest entry
+        # is first.
+        # 
+        # This is only updated when config.developer is True.
+        self.load_log = [ ]
+
+                
+    def init(self):
+        """
+        Updates the cache object to make use of settings that might be provided
+        by the game-maker.
+        """
+        
+        self.cache_limit = renpy.config.image_cache_size * renpy.config.screen_width * renpy.config.screen_height
+        
+    def quit(self): #@ReservedAssignment
+        if not self.preload_thread.isAlive():
+            return
+
+        with self.preload_lock:
+            self.keep_preloading = False
+            self.preload_lock.notify()
+
+        self.preload_thread.join()
+        
+        
+    # Clears out the cache.
+    def clear(self):
+        
+        self.lock.acquire()
+
+        self.preloads = [ ]
+        self.pin_cache = { }
+        self.cache = { }
+        self.first_preload_in_tick = True
+        self.size_of_current_generation = 0
+        self.total_cache_size = 0
+
+        self.added.clear()
+        
+        self.lock.release()
+    
+    # Increments time, and clears the list of images to be
+    # preloaded.
+    def tick(self):
+
+        with self.lock:
+            self.time += 1
+            self.preloads = [ ]
+            self.first_preload_in_tick = True
+            self.size_of_current_generation = 0
+            self.added.clear()
+            
+        if renpy.config.debug_image_cache:
+            renpy.display.ic_log.write("----")
+            filename, line = renpy.exports.get_filename_line()
+            renpy.display.ic_log.write("%s %d", filename, line)
+            
+    # The preload thread can deal with this update, so we don't need
+    # to lock things. 
+    def end_tick(self):
+        self.preloads = [ ]
+
+        
+    # This returns the pygame surface corresponding to the provided
+    # image. It also takes care of updating the age of images in the
+    # cache to be current, and maintaining the size of the current
+    # generation of images.
+    def get(self, image, predict=False):
+
+        if not isinstance(image, ImageBase):
+            raise Exception("Expected an image of some sort, but got" + str(image) + ".")
+
+        if not image.cache:
+            surf = image.load()                
+            renpy.display.render.mutated_surface(surf)
+            return surf
+
+        # First try to grab the image out of the cache without locking it.
+        ce = self.cache.get(image, None)
+
+        # Otherwise, we load the image ourselves.
+        if ce is None:
+
+            try:
+                if image in self.pin_cache:
+                    surf = self.pin_cache[image]
+                else:
+                    surf = image.load()
+                    
+            except:
+                raise
+            
+            with self.lock:
+            
+                ce = CacheEntry(image, surf)
+    
+                if image not in self.cache:
+                    self.total_cache_size += ce.size
+                
+                self.cache[image] = ce
+    
+                # Indicate that this surface had changed.
+                renpy.display.render.mutated_surface(ce.surf)
+    
+                if renpy.config.debug_image_cache:
+                    if predict:
+                        renpy.display.ic_log.write("Added %r (%.02f%%)", ce.what, 100.0 * self.total_cache_size / self.cache_limit)
+                    else:
+                        renpy.display.ic_log.write("Total Miss %r", ce.what)
+                        
+                renpy.display.draw.load_texture(ce.surf)
+
+                        
+        # Move it into the current generation. This isn't protected by
+        # a lock, so in certain circumstances we could have an
+        # inaccurate size - but that will be cured at the end of the
+        # current generation.
+        
+        if ce.time != self.time:
+            ce.time = self.time
+            self.size_of_current_generation += ce.size
+
+        # Done... return the surface.
+        return ce.surf
+
+    
+    # This kills off a given cache entry.
+    def kill(self, ce):
+
+        # Should never happen... but...
+        if ce.time == self.time:
+            self.size_of_current_generation -= ce.size
+
+        self.total_cache_size -= ce.size
+        del self.cache[ce.what]
+
+        if renpy.config.debug_image_cache:
+            renpy.display.ic_log.write("Removed %r", ce.what)
+
+    def cleanout(self):
+        """
+        Cleans out the cache, if it's gotten too large. Returns True
+        if the cache is smaller than the size limit, or False if it's
+        bigger and we don't want to continue preloading.
+        """
+
+        # If we're within the limit, return.
+        if self.total_cache_size <= self.cache_limit:
+            return True
+
+        # If we're outside the cache limit, we need to go and start
+        # killing off some of the entries until we're back inside it.
+        
+        for ce in sorted(iter(self.cache.values()), key=lambda a : a.time):
+        
+            if ce.time == self.time:
+                # If we're bigger than the limit, and there's nothing
+                # to remove, we should stop the preloading right away.
+                return False
+
+            # Otherwise, kill off the given cache entry.
+            self.kill(ce)
+
+            # If we're in the limit, we're done.
+            if self.total_cache_size <= self.cache_limit:
+                break
+
+        return True
+            
+
+    # Called to report that a given image would like to be preloaded.
+    def preload_image(self, im):
+
+        if not isinstance(im, ImageBase):
+            return
+            
+        with self.lock:
+
+            if im in self.added:
+                return
+
+            self.added.add(im)
+
+            if im in self.cache:
+                self.get(im)
+                in_cache = True
+            else:
+                self.preloads.append(im)
+                in_cache = False
+
+        if not in_cache:
+                
+            with self.preload_lock:
+                self.preload_lock.notify()
+
+        if in_cache and renpy.config.debug_image_cache:
+            renpy.display.ic_log.write("Kept %r", im)
+
+
+    def start_prediction(self):
+        """
+        Called at the start of prediction, to ensure the thread runs 
+        at least once to clean out the cache.
+        """
+
+        with self.preload_lock:
+            self.preload_lock.notify()
+
+    def preload_thread_main(self):
+
+        while self.keep_preloading:
+
+            self.preload_lock.acquire()
+            self.preload_lock.wait()
+            self.preload_lock.release()
+
+            while self.preloads and self.keep_preloading:
+        
+                start = time.time()
+                
+                # If the size of the current generation is bigger than the
+                # total cache size, stop preloading.
+                with self.lock:
+                    
+                    # If the cache is overfull, clean it out.
+                    if not self.cleanout():
+
+                        if renpy.config.debug_image_cache:
+                            for i in self.preloads:
+                                renpy.display.ic_log.write("Overfull %r", i)
+
+                        self.preloads = [ ]
+                        
+                        break
+
+                try:
+                    image = self.preloads.pop(0)                    
+
+                    if image not in self.preload_blacklist:
+                        try:
+                            self.get(image, True)
+                        except:
+                            self.preload_blacklist.add(image)                        
+                except:
+                    pass
+
+            with self.lock:
+                self.cleanout()
+            
+            # If we have time, preload pinned images.
+            if self.keep_preloading and not renpy.game.less_memory:
+
+                workset = set(renpy.store._cache_pin_set)
+
+                # Remove things that are not in the workset from the pin cache,
+                # and remove things that are in the workset from pin cache.  
+                for i in list(self.pin_cache.keys()):
+
+                    if i in workset:
+                        workset.remove(i)
+                    else:
+                        surf = self.pin_cache[i]
+
+                        del self.pin_cache[i]
+                        
+                            
+                # For each image in the worklist...                
+                for image in workset:
+
+                    if image in self.preload_blacklist:
+                        continue
+                    
+                    # If we have normal preloads, break out.
+                    if self.preloads:
+                        break
+
+                    try:
+                        surf = image.load()
+                        self.pin_cache[image] = surf
+                        renpy.display.draw.load_texture(surf)                        
+                    except:
+                        self.preload_blacklist.add(image)
+
+    def add_load_log(self, filename):
+        
+        if not renpy.config.developer:
+            return
+        
+        preload = (threading.current_thread() is self.preload_thread)
+    
+        self.load_log.insert(0, (time.time(), filename, preload))
+    
+        while len(self.load_log) > 100:
+            self.load_log.pop()
+        
+
+
+# The cache object.
+cache = Cache()
+
+def free_memory():
+    """
+    Frees some memory.
+    """
+
+    renpy.display.draw.free_memory()
+    cache.clear()
+
+
+class ImageBase(renpy.display.core.Displayable):
+    """
+    This is the base class for all of the various kinds of images that
+    we can possibly have.
+    """
+
+    __version__ = 1
+
+    def after_upgrade(self, version):
+        if version < 1:
+            self.cache = True
+    
+    def __init__(self, *args, **properties):
+
+        self.rle = properties.pop('rle', None)
+        self.cache = properties.pop('cache', True)
+            
+        properties.setdefault('style', 'image')
+
+        super(ImageBase, self).__init__(**properties)
+        self.identity = (type(self).__name__, ) + args
+
+
+    def __hash__(self):
+        return hash(self.identity)
+
+    def __eq__(self, other):
+
+        if not isinstance(other, ImageBase):
+            return False
+        
+        return self.identity == other.identity
+
+    def __repr__(self):
+        return "<" + " ".join([repr(i) for i in self.identity]) + ">"
+        
+    def load(self):
+        """
+        This function is called by the image cache code to cause this
+        image to be loaded. It's expected that children of this class
+        would override this.
+        """
+
+        assert False
+        
+    def render(self, w, h, st, at):
+        
+        im = cache.get(self)
+        texture = renpy.display.draw.load_texture(im)
+
+        w, h = im.get_size()
+        rv = renpy.display.render.Render(w, h)
+        rv.blit(texture, (0, 0))
+        return rv
+
+    def predict_one(self):
+        renpy.display.predict.image(self)
+
+    def predict_files(self):
+        """
+        Returns a list of files that will be accessed when this image
+        operation is performed.
+        """
+
+        return [ ]
+
+class Image(ImageBase):
+    """
+    This image manipulator loads an image from a file.
+    """
+
+    def __init__(self, filename, **properties):
+        """
+        @param filename: The filename that the image will be loaded from.
+        """
+
+        super(Image, self).__init__(filename, **properties)
+        self.filename = filename
+
+    def get_mtime(self):
+        return renpy.loader.get_mtime(self.filename)
+        
+    def load(self, unscaled=False):
+
+        cache.add_load_log(self.filename)
+
+        try:
+
+            if unscaled:
+                surf = renpy.display.pgrender.load_image_unscaled(renpy.loader.load(self.filename), self.filename)
+            else:
+                surf = renpy.display.pgrender.load_image(renpy.loader.load(self.filename), self.filename)
+
+            return surf
+
+        except Exception as e:
+
+            if renpy.config.missing_image_callback:
+                im = renpy.config.missing_image_callback(self.filename)
+                if im is None:
+                    raise e
+
+                return im.load()
+
+            raise
+        
+    def predict_files(self):
+
+        if renpy.loader.loadable(self.filename):
+            return [ self.filename ]
+        else:
+            if renpy.config.missing_image_callback:
+                im = renpy.config.missing_image_callback(self.filename)
+                if im is not None:
+                    return im.predict_files()
+
+            return [ self.filename ]
+
+class ZipFileImage(ImageBase):
+
+    def __init__(self, zipfilename, filename, mtime=0, **properties):
+        super(ZipFileImage, self).__init__(zipfilename, filename, mtime, **properties)
+
+        self.zipfilename = zipfilename
+        self.filename = filename
+
+    def load(self):
+        try:
+            zf = zipfile.ZipFile(self.zipfilename, 'r')
+            data = zf.read(self.filename)
+            sio = io.StringIO(data)
+            rv = renpy.display.pgrender.load_image(sio, self.filename)
+            zf.close()
+            return rv
+        except:
+            return renpy.display.pgrender.surface((2, 2), True)        
+    
+        
+
+    def predict_files(self):
+        return [ ]
+        
+    
+        
+class Composite(ImageBase):
+    """
+    :doc: im_im
+    
+    This image manipulator composites multiple images together to
+    form a single image.
+
+    The `size` should be a (width, height) tuple giving the size
+    of the composed image.
+
+    The remaining positional arguments are interpreted as groups of
+    two. The first argument in a group should be an (x, y) tuple,
+    while the second should be an image manipulator. The image
+    produced by the image manipulator is composited at the location
+    given by the tuple.
+
+    ::
+    
+        image girl clothed happy = im.Composite(
+            (300, 600),
+            (0, 0), "girl_body.png",
+            (0, 0), "girl_clothes.png",
+            (100, 100), "girl_happy.png"
+            )
+
+    """
+
+    def __init__(self, size, *args, **properties):
+
+        super(Composite, self).__init__(size, *args, **properties)
+
+        if len(args) % 2 != 0:
+            raise Exception("Composite requires an odd number of arguments.")
+
+        self.size = size
+        self.positions = args[0::2]
+        self.images = [ image(i) for i in args[1::2] ]
+
+    def get_mtime(self):
+        return min(i.get_mtime() for i in self.images)
+        
+    def load(self):
+
+        if self.size:
+            size = self.size
+        else:
+            size = cache.get(self.images[0]).get_size()
+
+        rv = renpy.display.pgrender.surface(size, True)
+
+        for pos, im in zip(self.positions, self.images):
+            rv.blit(cache.get(im), pos)
+
+        return rv
+
+    def predict_files(self):
+
+        rv = [ ]
+
+        for i in self.images:
+            rv.extend(i.predict_files())
+
+        return rv
+
+class Scale(ImageBase):
+    """
+    :doc: im_im
+
+    An image manipulator that scales `im` (an image manipulator) to
+    `width` and `height`.
+
+    If `bilinear` is true, then bilinear interpolation is used for
+    the scaling. Otherwise, nearest neighbor interpolation is used.
+
+    ::
+
+        image logo scale = im.Scale("logo.png", 100, 150) 
+    """
+
+    def __init__(self, im, width, height, bilinear=True, **properties):
+
+        im = image(im)
+        super(Scale, self).__init__(im, width, height, bilinear, **properties)
+
+        self.image = im
+        self.width = int(width)
+        self.height = int(height)
+        self.bilinear = bilinear
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+        
+    def load(self):
+
+        child = cache.get(self.image)
+        
+        if self.bilinear:
+            try:
+                renpy.display.render.blit_lock.acquire()
+                rv = renpy.display.scale.smoothscale(child, (self.width, self.height))
+            finally:
+                renpy.display.render.blit_lock.release()
+        else:
+            try:
+                renpy.display.render.blit_lock.acquire()
+                rv = renpy.display.pgrender.transform_scale(child, (self.width, self.height))
+            finally:
+                renpy.display.render.blit_lock.release()
+            
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+class FactorScale(ImageBase):
+    """
+    :doc: im_im
+
+    An image manipulator that scales `im` (a second image manipulator)
+    to `width` times its original `width`, and `height` times its
+    original height. If `height` is ommitted, it defaults to `width`.
+
+    If `bilinear` is true, then bilinear interpolation is used for
+    the scaling. Otherwise, nearest neighbor interpolation is used.
+
+    ::
+
+        image logo doubled = im.FactorScale("logo.png", 1.5)
+    """
+
+
+    def __init__(self, im, width, height=None, bilinear=True, **properties):
+
+        if height is None:
+            height = width
+        
+        im = image(im)
+        super(FactorScale, self).__init__(im, width, height, bilinear, **properties)
+
+        self.image = im
+        self.width = width
+        self.height = height
+        self.bilinear = bilinear
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+
+        surf = cache.get(self.image)
+        width, height = surf.get_size()
+
+        width = int(width * self.width)
+        height = int(height * self.height)
+
+        if self.bilinear:
+            try:
+                renpy.display.render.blit_lock.acquire()
+                rv = renpy.display.scale.smoothscale(surf, (width, height))
+            finally:
+                renpy.display.render.blit_lock.release()
+
+        else:
+            try:
+                renpy.display.render.blit_lock.acquire()
+                rv = renpy.display.pgrender.transform_scale(surf, (width, height))
+            finally:
+                renpy.display.render.blit_lock.release()
+            
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+
+class Flip(ImageBase):
+    """
+    :doc: im_im
+
+    An image manipulator that flips `im` (an image manipulator)
+    vertically or horizontally.  `vertical` and `horizontal` control
+    the directions in which the image is flipped.
+
+    ::
+    
+        image eileen flip = im.Flip("eileen_happy.png", vertical=True)
+    """
+
+    def __init__(self, im, horizontal=False, vertical=False, **properties):
+
+        if not (horizontal or vertical):
+            raise Exception("im.Flip must be called with a true value for horizontal or vertical.")
+
+        im = image(im)
+        super(Flip, self).__init__(im, horizontal, vertical, **properties)
+
+        self.image = im
+        self.horizontal = horizontal
+        self.vertical = vertical
+
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+        
+    def load(self):
+
+        child = cache.get(self.image)
+        
+        try:
+            renpy.display.render.blit_lock.acquire()
+            rv = renpy.display.pgrender.flip(child, self.horizontal, self.vertical)
+        finally:
+            renpy.display.render.blit_lock.release()
+
+        return rv
+
+    
+    def predict_files(self):
+        return self.image.predict_files()
+
+    
+
+class Rotozoom(ImageBase):
+    """
+    This is an image manipulator that is a smooth rotation and zoom of another image manipulator.
+    """
+
+    def __init__(self, im, angle, zoom, **properties):
+        """
+        @param im: The image to be rotozoomed.
+
+        @param angle: The number of degrees counterclockwise the image is
+        to be rotated.
+
+        @param zoom: The zoom factor. Numbers that are greater than 1.0
+        lead to the image becoming larger.
+        """
+
+        im = image(im)
+        super(Rotozoom, self).__init__(im, angle, zoom, **properties)
+
+        self.image = im
+        self.angle = angle
+        self.zoom = zoom
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+
+        child = cache.get(self.image)
+        
+        try:
+            renpy.display.render.blit_lock.acquire()
+            rv = renpy.display.pgrender.rotozoom(child, self.angle, self.zoom)
+        finally:
+            renpy.display.render.blit_lock.release()
+
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+        
+        
+class Crop(ImageBase):
+    """
+    :doc: im_im 
+    :args: (im, rect)
+    
+    An image manipulator that crops `rect`, a (x, y, width, height) tuple,
+    out of `im`, an image manipulator.
+
+    ::
+    
+        image logo crop = im.Crop("logo.png", (0, 0, 100, 307))
+    """
+
+    def __init__(self, im, x, y=None, w=None, h=None, **properties):
+
+        im = image(im)
+
+        if y is None:
+            (x, y, w, h) = x
+        
+        super(Crop, self).__init__(im, x, y, w, h, **properties)
+
+        self.image = im
+        self.x = x
+        self.y = y
+        self.w = w
+        self.h = h
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+        return cache.get(self.image).subsurface((self.x, self.y,
+                                                 self.w, self.h))
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+
+ramp_cache = { }
+
+
+def ramp(start, end):
+    """
+    Returns a 256 character linear ramp, where the first character has
+    the value start and the last character has the value end. Such a
+    ramp can be used as a map argument of im.Map.
+    """
+
+    rv = ramp_cache.get((start, end), None)
+    if rv is None:
+
+        chars = [ ]
+
+        for i in range(0, 256):
+            i = i / 255.0
+            chars.append(chr(int( end * i + start * (1.0 - i) ) ) )
+            
+        rv = "".join(chars)
+        ramp_cache[start, end] = rv
+
+    return rv
+
+identity = ramp(0, 255)
+
+class Map(ImageBase):
+    """
+    This adjusts the colors of the image that is its child. It takes
+    as arguments 4 256 character strings. If a pixel channel has a
+    value of 192, then the value of the 192nd character in the string
+    is used for the mapped pixel component.
+    """
+
+    def __init__(self, im, rmap=identity, gmap=identity, bmap=identity,
+                 amap=identity, force_alpha=False, **properties):
+
+        im = image(im)
+
+        super(Map, self).__init__(im, rmap, gmap, bmap, amap, force_alpha, **properties)
+        
+        self.image = im
+        self.rmap = rmap
+        self.gmap = gmap
+        self.bmap = bmap
+        self.amap = amap
+
+        self.force_alpha = force_alpha
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+
+        surf = cache.get(self.image)
+
+        rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+        renpy.display.module.map(surf, rv,
+                                 self.rmap, self.gmap, self.bmap, self.amap)
+
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+class Twocolor(ImageBase):
+    """
+    This takes as arguments two colors, white and black. The image is
+    mapped such that pixels in white have the white color, pixels in
+    black have the black color, and shades of gray are linearly
+    interpolated inbetween.  The alpha channel is mapped linearly
+    between 0 and the alpha found in the white color, the black
+    color's alpha is ignored.
+    """
+
+    def __init__(self, im, white, black, force_alpha=False, **properties):
+
+        white = renpy.easy.color(white)
+        black = renpy.easy.color(black)
+
+        im = image(im)
+
+        super(Twocolor, self).__init__(im, white, black, force_alpha, **properties)
+        
+        self.image = im
+        self.white = white
+        self.black = black
+
+        self.force_alpha = force_alpha
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+
+        surf = cache.get(self.image)
+
+        rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+        renpy.display.module.twomap(surf, rv,
+                                    self.white, self.black)
+
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+
+class Recolor(ImageBase):
+    """
+    This adjusts the colors of the image that is its child. It takes as an
+    argument 4 numbers between 0 and 255, and maps each channel of the image
+    linearly between 0 and the supplied color.
+    """
+
+    def __init__(self, im, rmul=255, gmul=255, bmul=255,
+                 amul=255, force_alpha=False, **properties):
+
+        im = image(im)
+
+        super(Recolor, self).__init__(im, rmul, gmul, bmul, amul, force_alpha, **properties)
+        
+        self.image = im
+        self.rmul = rmul + 1
+        self.gmul = gmul + 1
+        self.bmul = bmul + 1
+        self.amul = amul + 1
+
+        self.force_alpha = force_alpha
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+
+        surf = cache.get(self.image)
+
+        rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+        renpy.display.module.linmap(surf, rv,
+                                    self.rmul, self.gmul, self.bmul, self.amul)
+
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+class MatrixColor(ImageBase):
+    """
+    :doc: im_matrixcolor
+    
+    An image operator that uses `matrix` to linearly transform the
+    image manipulator `im`.
+
+    `Matrix` should be a list, tuple, or :func:`im.matrix` that is 20
+    or 25 elements long. If the object has 25 elements, then elements
+    past the 20th are ignored.
+
+    When the four components of the source color are R, G, B, and A,
+    which range from 0.0 to 1.0; the four components of the transformed
+    color are R', G', B', and A', with the same range; and the elements
+    of the matrix are named::
+    
+        [ a, b, c, d, e,
+          f, g, h, i, j,
+          k, l, m, n, o,
+          p, q, r, s, t ]
+
+    the transformed colors can be computed with the formula::
+
+        R' = (a * R) + (b * G) + (c * B) + (d * A) + e
+        G' = (f * R) + (g * G) + (h * B) + (i * A) + j
+        B' = (k * R) + (l * G) + (m * B) + (n * A) + o
+        A' = (p * R) + (q * G) + (r * B) + (s * A) + t
+
+    The components of the transformed color are clamped to the
+    range [0.0, 1.0].
+    """
+
+    def __init__(self, im, matrix, **properties):
+
+        im = image(im)
+
+        if len(matrix) != 20 and len(matrix) != 25:
+            raise Exception("ColorMatrix expects a 20 or 25 element matrix, got %d elements." % len(matrix))
+
+        matrix = tuple(matrix)        
+        super(MatrixColor, self).__init__(im, matrix, **properties)
+        
+        self.image = im
+        self.matrix = matrix
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+        
+    def load(self):
+
+        surf = cache.get(self.image)
+
+        rv = renpy.display.pgrender.surface(surf.get_size(), True)
+
+        renpy.display.module.colormatrix(surf, rv, self.matrix)
+        
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+class matrix(tuple):
+    """
+    :doc: im_matrixcolor
+    
+    Constructs an im.matrix object from `matrix`. im.matrix objects
+    support The operations supported are matrix multiplication, scalar
+    multiplication, element-wise addition, and element-wise
+    subtraction. These operations are invoked using the standard
+    mathematical operators (\\*, \\*, +, and -, respectively). If two
+    im.matrix objects are multiplied, matrix multiplication is
+    performed, otherwise scalar multiplication is used.
+
+    `matrix` is a 20 or 25 element list or tuple. If it is 20 elements
+    long, it is padded with (0, 0, 0, 0, 1) to make a 5x5 matrix,
+    suitable for multiplication.    
+    """
+
+    def __new__(cls, *args):
+
+        if len(args) == 1:
+            args = tuple(args[0])
+
+        if len(args) == 20:
+            args = args + (0, 0, 0, 0, 1)
+
+        if len(args) != 25:
+            raise Exception("Matrix expects to be given 20 or 25 entries, not %d." % len(args))
+
+        return tuple.__new__(cls, args)
+    
+    def mul(self, a, b):
+
+        if not isinstance(a, matrix):
+            a = matrix(a)
+
+        if not isinstance(b, matrix):
+            b = matrix(b)
+            
+        result = [ 0 ] * 25
+        for y in range(0, 5):
+            for x in range(0, 5):
+                for i in range(0, 5):
+                    result[x + y * 5] += a[x + i * 5] * b[i + y * 5]
+                    
+        return matrix(result)
+
+    def scalar_mul(self, other):
+        other = float(other)
+        return matrix([ i * other for i in self ])
+
+    def vector_mul(self, o):
+        
+        return (o[0]*self[0] + o[1]*self[1] + o[2]*self[2] + o[3]*self[3] + self[4],
+                o[0]*self[5] + o[1]*self[6] + o[2]*self[7] + o[3]*self[8] + self[9],
+                o[0]*self[10] + o[1]*self[11] + o[2]*self[12] + o[3]*self[13] + self[14],
+                o[0]*self[15] + o[1]*self[16] + o[2]*self[17] + o[3]*self[18] + self[19],
+                1)
+
+                 
+    def __add__(self, other):
+        if isinstance(other, (int, float)):
+            other = float(other)
+            return matrix([ i + other for i in self ])
+
+        other = matrix(other)
+        return matrix([ i + j for i, j in zip(self, other)])
+
+    __radd__ = __add__
+
+    def __sub__(self, other):
+        return self + other * -1
+
+    def __rsub__(self, other):
+        return self * -1 + other
+        
+    def __mul__(self, other):
+        if isinstance(other, (int, float)):
+            return self.scalar_mul(other)
+
+        return self.mul(self, other)
+    
+    def __rmul__(self, other):
+        if isinstance(other, (int, float)):
+            return self.scalar_mul(other)
+        
+        return self.mul(other, self)
+
+    def __repr__(self):
+        return """\
+im.matrix(%f, %f, %f, %f, %f.
+          %f, %f, %f, %f, %f,
+          %f, %f, %f, %f, %f,
+          %f, %f, %f, %f, %f,
+          %f, %f, %f, %f, %f)""" % self
+
+
+    @staticmethod
+    def identity():
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.identity
+        
+        Returns an identity matrix, one that does not change color or
+        alpha.
+        """        
+        
+        return matrix(1, 0, 0, 0, 0,
+                      0, 1, 0, 0, 0,
+                      0, 0, 1, 0, 0,
+                      0, 0, 0, 1, 0)
+    @staticmethod
+    def saturation(level, desat=(0.2126, 0.7152, 0.0722)):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.saturation
+        
+        Returns an im.matrix that alters the saturation of an
+        image. The alpha channel is untouched.
+
+        `level`        
+            The amount of saturation in the resulting image. 1.0 is
+            the unaltered image, while 0.0 is grayscale.
+        
+        `desat`        
+            This is a 3-element tuple that controls how much of the
+            red, green, and blue channels will be placed into all
+            three channels of a fully desaturated image. The default
+            is based on the constants used for the luminance channel
+            of an NTSC television signal. Since the human eye is
+            mostly sensitive to green, more of the green channel is
+            kept then the other two channels.
+        """
+        
+        r, g, b = desat
+        
+        def I(a, b):
+            return a + (b - a) * level
+
+        return matrix(I(r, 1), I(g, 0), I(b, 0), 0, 0,
+                      I(r, 0), I(g, 1), I(b, 0), 0, 0,
+                      I(r, 0), I(g, 0), I(b, 1), 0, 0,
+                      0, 0, 0, 1, 0)
+
+    @staticmethod
+    def desaturate():
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.desaturate
+
+        Returns an im.matrix that desaturates the image (makes it
+        grayscale). This is equivalent to calling
+        im.matrix.saturation(0).
+        """
+
+        return matrix.saturation(0.0)
+
+    @staticmethod
+    def tint(r, g, b):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.tint
+
+        Returns an im.matrix that tints an image, without changing
+        the alpha channel. `r`, `g`, and `b` should be numbers between
+        0 and 1, and control what fraction of the given channel is
+        placed into the final image. (For example, if `r` is .5, and
+        the value of the red channel is 100, the transformed color
+        will have a red value of 50.)
+        """
+
+        return matrix(r, 0, 0, 0, 0,
+                      0, g, 0, 0, 0,
+                      0, 0, b, 0, 0,
+                      0, 0, 0, 1, 0)
+
+    @staticmethod
+    def invert():
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.invert
+
+        Returns an im.matrix that inverts the red, green, and blue
+        channels of the image without changing the alpha channel.
+        """
+
+        return matrix(-1, 0, 0, 0, 1,
+                      0, -1, 0, 0, 1,
+                      0, 0, -1, 0, 1,
+                      0, 0, 0, 1, 0)
+
+    @staticmethod
+    def brightness(b):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.brightness
+
+        Returns an im.matrix that alters the brightness of an image.
+
+        `b`
+            The amount of change in image brightness. This should be
+            a number between -1 and 1, with -1 the darkest possible
+            image and 1 the brightest.
+        """
+
+        return matrix(1, 0, 0, 0, b,
+                      0, 1, 0, 0, b,
+                      0, 0, 1, 0, b,
+                      0, 0, 0, 1, 0)
+
+    @staticmethod
+    def opacity(o):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.opacity
+
+        Returns an im.matrix that alters the opacity of an image. An
+        `o` of 0.0 is fully transparent, while 1.0 is fully opaque.
+        """
+        
+        return matrix(1, 0, 0, 0, 0,
+                      0, 1, 0, 0, 0,
+                      0, 0, 1, 0, 0,
+                      0, 0, 0, o, 0)
+    
+    @staticmethod
+    def contrast(c):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.contrast
+
+        Returns an im.matrix that alters the contrast of an image. `c` should
+        be greater than 0.0, with values between 0.0 and 1.0 decreasing contrast, and
+        values greater than 1.0 increasing contrast.
+        """
+
+        return matrix.brightness(-.5) * matrix.tint(c, c, c) * matrix.brightness(.5)     
+
+    # from http://www.gskinner.com/blog/archives/2005/09/flash_8_source.html
+    @staticmethod
+    def hue(h):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.hue
+        
+        Returns an im.matrix that rotates the hue by `h` degrees, while
+        preserving luminosity.
+        """
+
+        h = h * math.pi / 180
+        cosVal = math.cos(h)
+        sinVal = math.sin(h)
+        lumR = 0.213
+        lumG = 0.715
+        lumB = 0.072
+        return matrix(
+            lumR+cosVal*(1-lumR)+sinVal*(-lumR),lumG+cosVal*(-lumG)+sinVal*(-lumG),lumB+cosVal*(-lumB)+sinVal*(1-lumB),0,0,
+            lumR+cosVal*(-lumR)+sinVal*(0.143),lumG+cosVal*(1-lumG)+sinVal*(0.140),lumB+cosVal*(-lumB)+sinVal*(-0.283),0,0,
+            lumR+cosVal*(-lumR)+sinVal*(-(1-lumR)),lumG+cosVal*(-lumG)+sinVal*(lumG),lumB+cosVal*(1-lumB)+sinVal*(lumB),0,0,
+            0,0,0,1,0,
+            0,0,0,0,1
+            )
+
+    @staticmethod
+    def colorize(black_color, white_color):
+        """
+        :doc: im_matrixcolor
+        :name: im.matrix.colorize
+        
+        Returns an im.matrix that colorizes a black and white image.
+        `black_color` and `white_color` are Ren'Py style colors, so
+        they may be specfied as strings or tuples of (0-255) color
+        values. ::
+
+            # This makes black colors red, and white colors blue.
+            image logo colored = im.MatrixColor(
+                "bwlogo.png",
+                im.matrix.colorize("#f00", "#00f"))
+
+        """
+
+        (r0, g0, b0, _a0) = renpy.easy.color(black_color)
+        (r1, g1, b1, _a1) = renpy.easy.color(white_color)
+
+        r0 /= 255.0
+        g0 /= 255.0
+        b0 /= 255.0
+        r1 /= 255.0
+        g1 /= 255.0
+        b1 /= 255.0
+        
+        return matrix((r1-r0), 0, 0, 0, r0,
+                      0, (g1-g0), 0, 0, g0,
+                      0, 0, (b1-b0), 0, b0,
+                      0, 0, 0, 1, 0)
+
+    
+    
+def Grayscale(im, desat=(0.2126, 0.7152, 0.0722), **properties):
+    """
+    :doc: im_im
+    :args: (im, **properties)
+
+    An image manipulator that creats a desaturated version of the image
+    manipulator `im`.
+    """
+
+    return MatrixColor(im, matrix.saturation(0.0, desat), **properties)
+
+
+def Sepia(im, tint=(1.0, .94, .76), desat=(0.2126, 0.7152, 0.0722), **properties):
+    """
+    :doc: im_im
+    :args: (im, **properties)
+
+    An image manipulator that creates a sepia-toned version of the image
+    manipulator `im`.
+    """
+    
+    return MatrixColor(im, matrix.saturation(0.0, desat) * matrix.tint(tint[0], tint[1], tint[2]), **properties)
+    
+
+def Color(im, color):
+    """
+    This recolors the supplied image, mapping colors such that black is
+    black and white is the supplied color.
+    """
+
+    r, g, b, a = renpy.easy.color(color)
+
+    return Recolor(im, r, g, b, a)
+
+
+def Alpha(image, alpha, **properties):
+    """
+    Returns an alpha-mapped version of the image. Alpha is the maximum
+    alpha that this image can have, a number between 0.0 (fully
+    transparent) and 1.0 (opaque).
+
+    If an image already has an alpha channel, values in that alpha
+    channel are reduced as appropriate.
+    """
+
+    return Recolor(image, 255, 255, 255, int(255 * alpha), force_alpha=True, **properties)
+
+class Tile(ImageBase):
+    """
+    :doc: im_im
+
+    An image manipulator that tiles the image manipulator `im`, until
+    it is `size`.
+
+    `size`
+        If not None, a (width, height) tuple. If None, this defaults to
+        (:var:`config.screen_width`, :var:`config.screen_height`).
+    """
+
+    def __init__(self, im, size=None, **properties):
+
+        im = image(im)
+
+        super(Tile, self).__init__(im, size, **properties)
+        self.image = im
+        self.size = size
+
+    def get_mtime(self):
+        return self.image.get_mtime()
+
+    def load(self):
+
+        size = self.size
+
+        if size is None:
+            size = (renpy.config.screen_width, renpy.config.screen_height)
+
+        surf = cache.get(self.image)
+
+        rv = renpy.display.pgrender.surface(size, True)
+
+        width, height = size
+        sw, sh = surf.get_size()
+
+        for y in range(0, height, sh):
+            for x in range(0, width, sw):
+                rv.blit(surf, (x, y))
+
+        return rv
+
+    def predict_files(self):
+        return self.image.predict_files()
+
+class AlphaMask(ImageBase):
+    """
+    :doc: im_im
+    
+    An image manipulator that takes two image manipulators, `base` and
+    `mask`, as arguments. It replaces the alpha channel of `base` with
+    the red channel of `mask`.
+
+    This is used to provide an image's alpha channel in a second
+    image, like having one jpeg for color data, and a second one
+    for alpha. In some cases, two jpegs can be smaller than a
+    single png file.
+    """
+    
+    def __init__(self, base, mask, **properties):
+        super(AlphaMask, self).__init__(base, mask, **properties)
+
+        self.base = image(base)
+        self.mask = image(mask)
+
+    def get_mtime(self):
+        return max(self.base.get_mtime(), self.image.get_mtime())
+
+    def load(self):
+
+        basesurf = cache.get(self.base)
+        masksurf = cache.get(self.mask)
+
+        if basesurf.get_size() != masksurf.get_size():
+            raise Exception("AlphaMask surfaces must be the same size.")
+
+        # Used to copy the surface.
+        rv = renpy.display.pgrender.copy_surface(basesurf)
+        renpy.display.module.alpha_munge(masksurf, rv, identity)
+            
+        return rv
+            
+    def predict_files(self):
+        return self.base.predict_files() + self.mask.predict_files()
+        
+def image(arg, loose=False, **properties):
+    """
+    :doc: im_image
+    :name: Image
+    :args: (filename, **properties)
+
+    Loads an image from a file. `filename` is a
+    string giving the name of the file.
+
+    `filename` should be a JPEG or PNG file with an appropriate
+    extension.
+    """
+
+    """
+    (Actually, the user documentation is a bit misleading, as
+     this tries for compatibility with several older forms of
+     image specification.)
+
+    If the loose argument is False, then this will report an error if an
+    arbitrary argument is given. If it's True, then the argument is passed
+    through unchanged.
+    """
+
+    if isinstance(arg, ImageBase):
+        return arg
+
+    elif isinstance(arg, str):
+        return Image(arg, **properties)
+
+    elif isinstance(arg, renpy.display.image.ImageReference):
+        arg.find_target()
+        return image(arg.target, loose=loose, **properties)
+            
+    elif isinstance(arg, tuple):
+        params = [ ]
+
+        for i in arg:
+            params.append((0, 0))
+            params.append(i)
+
+        return Composite(None, *params)
+
+    elif loose:
+        return arg
+
+    if isinstance(arg, renpy.display.core.Displayable):
+        raise Exception("Expected an image, but got a general displayable.")
+    else:
+        raise Exception("Could not construct image from argument.")
+
+
+def load_image(fn):
+    """
+    This loads an image from the given filename, using the cache.
+    """
+
+    surf = cache.get(image(fn))
+    return renpy.display.draw.load_texture(surf)
diff --git a/unrpyc/renpy/display/image.py b/unrpyc/renpy/display/image.py
new file mode 100644
index 0000000..362f87a
--- /dev/null
+++ b/unrpyc/renpy/display/image.py
@@ -0,0 +1,397 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains some miscellaneous displayables that involve images.
+# Most of the guts of this file have been moved into im.py, with only some
+# of the stuff thar uses images remaining.
+
+import renpy.display
+import renpy.text
+from renpy.display.render import render, Render
+
+import collections
+
+# A map from image name to the displayable object corresponding to that
+# image.
+images = { }
+    
+# A map from image tag to lists of possible attributes for images with that
+# tag.
+image_attributes = collections.defaultdict(list)
+
+def register_image(name, d):
+    """
+    Registers the existence of an image with `name`, and that the image
+    used displayable d.
+    """
+
+    tag = name[0]
+    rest = name[1:]
+
+    images[name] = d
+    image_attributes[tag].append(rest)
+
+
+def wrap_render(child, w, h, st, at):
+    rend = render(child, w, h, st, at)
+    rv = Render(rend.width, rend.height)
+    rv.blit(rend, (0, 0))
+    return rv
+
+class ImageReference(renpy.display.core.Displayable):
+    """
+    ImageReference objects are used to reference images by their name,
+    which is a tuple of strings corresponding to the name used to define
+    the image in an image statment.
+    """
+
+    nosave = [ 'target' ]
+    target = None
+    param_target = None
+    
+    def __init__(self, name, **properties):
+        """
+        @param name: A tuple of strings, the name of the image. Or else
+        a displayable, containing the image directly.
+        """
+        
+        super(ImageReference, self).__init__(**properties)
+
+        self.name = name
+
+    def _get_parameterized(self):
+        if self.param_target:
+            return self.param_target._get_parameterized()
+
+        return self
+        
+    def find_target(self):
+
+        if self.param_target:
+            self.target = self.param_target
+            return None
+
+        name = self.name
+
+        if isinstance(name, renpy.display.core.Displayable):
+            self.target = name
+            return True
+        
+        if not isinstance(name, tuple):
+            name = tuple(name.split())
+        
+        parameters = [ ]
+
+        def error(msg):
+            self.target = renpy.text.text.Text(msg, color=(255, 0, 0, 255), xanchor=0, xpos=0, yanchor=0, ypos=0)
+
+            if renpy.config.debug:
+                raise Exception(msg)
+
+            
+        # Scan through, searching for an image (defined with an
+        # input statement) that is a prefix of the given name.
+        while name:
+            if name in images:
+                target = images[name]
+
+                try:
+                    self.target = target.parameterize(name, parameters)
+                    if self.target is not target:
+                        self.param_target = self.target
+
+                except Exception as e:
+                    if renpy.config.debug:
+                        raise
+
+                    error(str(e))
+
+                return True
+
+            else:
+                parameters.insert(0, name[-1])
+                name = name[:-1]
+
+        error("Image '%s' not found." % ' '.join(self.name))
+        return False
+
+    def parameterize(self, name, parameters):
+        if not self.target:
+            self.find_target()
+  
+        return self.target.parameterize(name, parameters)
+
+    def _hide(self, st, at, kind):
+        if not self.target:
+            self.find_target()
+
+        return self.target._hide(st, at, kind)
+
+    def set_transform_event(self, event):
+        if not self.target:
+            self.find_target()
+
+        return self.target.set_transform_event(event)
+    
+    def event(self, ev, x, y, st):
+        if not self.target:
+            self.find_target()
+
+        return self.target.event(ev, x, y, st)
+        
+    def render(self, width, height, st, at):
+        if not self.target:
+            self.find_target()
+
+        return wrap_render(self.target, width, height, st, at)
+
+    def get_placement(self):
+        if not self.target:
+            self.find_target()
+
+        if not renpy.config.imagereference_respects_position:
+            return self.target.get_placement()
+            
+        xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.target.get_placement()
+
+        if xpos is None:
+            xpos = self.style.xpos
+
+        if ypos is None:
+            ypos = self.style.ypos
+
+        if xanchor is None:
+            xanchor = self.style.xanchor
+
+        if yanchor is None:
+            yanchor = self.style.yanchor
+            
+        return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+
+    def visit(self):
+        if not self.target:
+            self.find_target()
+
+        return [ self.target ]
+
+
+    
+class ShownImageInfo(renpy.object.Object):
+    """
+    This class keeps track of which images are being shown right now,
+    and what the attributes of those images are. (It's used for a similar
+    purpose during prediction, regarding the state in the future.)
+    """
+    
+    __version__ = 2
+
+    def __init__(self, old=None):
+        """
+        Creates a new object. If `old` is given, copies the default state
+        from old, otherwise initializes the object to a default state.
+        """
+        
+        if old is None:
+
+            # A map from (layer, tag) -> tuple of attributes
+            # This doesn't necessarily correspond to something that is
+            # currently showing, as we can remember the state of a tag
+            # for use in SideImage.
+            self.attributes = { }
+
+            # A set of (layer, tag) pairs that are being shown on the
+            # screen right now.
+            self.shown = set()
+
+        else:
+            self.attributes = old.attributes.copy()
+            self.shown = old.shown.copy()
+        
+    
+    def after_upgrade(self, version):
+        if version < 2:
+
+            self.attributes = { }
+            self.shown = set()
+            
+            for layer in self.images:
+                for tag in self.images[layer]:
+                    self.attributes[layer, tag] = self.images[layer][tag][1:]
+                    self.shown.add((layer, tag))
+               
+    def get_attributes(self, layer, tag):
+        """
+        Get the attributes associated the image with tag on the given 
+        layer.
+        """
+        
+        return self.attributes.get((layer, tag), ())
+                    
+    def showing(self, layer, name):
+        """
+        Returns true if name is the prefix of an image that is showing
+        on layer, or false otherwise.
+        """
+
+        tag = name[0]
+        rest = name[1:]
+
+        if (layer, tag) not in self.shown:
+            return None
+       
+        shown = self.attributes[layer, tag]
+        
+        if len(shown) < len(rest):
+            return False
+
+        for a, b in zip(shown, rest):
+            if a != b:
+                return False
+
+        return True
+
+    def predict_scene(self, layer):
+        """
+        Predicts the scene statement being called on layer.
+        """
+        
+        for l, t in list(self.attributes.keys()):
+            if l == layer:
+                del self.attributes[l, t]
+
+        self.shown = set((l, t) for l, t in self.shown if l != layer)
+
+    def predict_show(self, layer, name, show=True):
+        """
+        Predicts name being shown on layer.
+        
+        `show`
+            If True, the image will be flagged as being shown to the user. If 
+            False, only the attributes will be updated.
+        """
+
+        tag = name[0]
+        rest = name[1:]
+
+        self.attributes[layer, tag] = rest
+
+        if show:
+            self.shown.add((layer, tag))
+
+    def predict_hide(self, layer, name):
+        tag = name[0]
+
+        if (layer, tag) in self.attributes:
+            del self.attributes[layer, tag]
+
+        self.shown.discard((layer, tag))
+
+
+    def apply_attributes(self, layer, tag, name):
+        """
+        Given a layer, tag, and an image name (with attributes),
+        returns the canonical name of an image, if one exists. Raises
+        an exception if it's ambiguious, and returns None if an image
+        with that name couldn't be found.
+        """
+
+        # If the name matches one that exactly exists, return it.
+        if name in images:
+            return name
+        
+        nametag = name[0]
+        
+        # The set of attributes a matching image must have.
+        required = set(name[1:])
+
+        # The set of attributes a matching image may have.
+        optional = set(self.attributes.get((layer, tag), [ ]))
+
+        # Deal with banned attributes..
+        for i in name[1:]:
+            if i[0] == "-":
+                optional.discard(i[1:])
+                required.discard(i)
+
+        return self.choose_image(nametag, required, optional, name)
+
+    def choose_image(self, tag, required, optional, exception_name):
+        """        
+        """
+
+        # The longest length of an image that matches.
+        max_len = 0
+
+        # The list of matching images.
+        matches = None
+        
+        for attrs in image_attributes[tag]:
+
+            num_required = 0
+
+            for i in attrs:
+                if i in required:
+                    num_required += 1
+                    continue
+
+                elif i not in optional:
+                    break
+
+            else:
+                
+                # We don't have any not-found attributes. But we might not
+                # have all of the attributes.
+                
+                if num_required != len(required):
+                    continue
+
+                len_attrs = len(attrs)
+                
+                if len_attrs < max_len:
+                    continue
+
+                if len_attrs > max_len:
+                    max_len = len_attrs
+                    matches = [ ]
+
+                matches.append((tag, ) + attrs)
+
+        if matches is None:
+            return None
+
+        if len(matches) == 1:
+            return matches[0]
+
+        if exception_name:
+            raise Exception("Showing '" + " ".join(exception_name) + "' is ambiguous, possible images include: " + ", ".join(" ".join(i) for i in matches))
+        else:
+            return None
+
+renpy.display.core.ImagePredictInfo = ShownImageInfo
+
+                    
+# Functions that have moved from this module to other modules,
+# that live here for the purpose of backward-compatibility.
+Image = renpy.display.im.image
+Solid = renpy.display.imagelike.Solid
+Frame = renpy.display.imagelike.Frame
+ImageButton = renpy.display.behavior.ImageButton
+
diff --git a/unrpyc/renpy/display/imagelike.py b/unrpyc/renpy/display/imagelike.py
new file mode 100644
index 0000000..aea1d15
--- /dev/null
+++ b/unrpyc/renpy/display/imagelike.py
@@ -0,0 +1,382 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy.display
+from renpy.display.render import render, Render, Matrix2D
+
+# This file contains displayables that are image-like, because they take
+# up a rectangular area of the screen, and do not respond to input.
+
+class Solid(renpy.display.core.Displayable):
+    """
+    :doc: disp_imagelike
+
+    A displayable that fills the area its assigned with `color`.
+
+    ::
+    
+        image white = Solid("#fff")
+    
+    """
+
+    def __init__(self, color, **properties):
+
+        super(Solid, self).__init__(**properties)
+
+        if color is not None:
+            self.color = renpy.easy.color(color)
+        else:
+            self.color = None
+
+    def visit(self):
+        return [ ]
+        
+    def render(self, width, height, st, at):
+
+        color = self.color or self.style.color
+        
+        rv = Render(width, height)
+
+        if color is None or width <= 0 or height <= 0:
+            return rv
+
+        SIZE = 10
+
+        if width < SIZE or height < SIZE:
+            tex = renpy.display.draw.solid_texture(width, height, color)
+        else:
+            tex = renpy.display.draw.solid_texture(SIZE, SIZE, color)
+            rv.forward = Matrix2D(1.0 * SIZE / width, 0, 0, 1.0 * SIZE / height)
+            rv.reverse = Matrix2D(1.0 * width / SIZE, 0, 0, 1.0 * height / SIZE)
+        
+        rv.blit(tex, (0, 0))
+
+        return rv
+        
+class Frame(renpy.display.core.Displayable):
+    """
+    :doc: disp_imagelike
+    :args: (image, xborder, yborder, tile=False, **properties)
+    
+    A displayable that resizes an image to fill the available area,
+    while preserving the width and height of its borders.  is often
+    used as the background of a window or button.
+
+    .. figure:: frame_example.png
+
+        Using a frame to resize an image to double its size.
+
+    `image`
+        An image manipulator that will be resized by this frame.
+
+    `left`
+        The size of the border on the left side.
+        
+    `top`
+        The size of the border on the top.
+        
+    `right`
+        The size of the border on the right side. If None, defaults 
+        to `left`.
+        
+    `bottom`
+        The side of the border on the bottom. If None, defaults to `top`.
+
+    `tile`
+        If true, tiling is used to resize sections of the image,
+        rather than scaling.
+
+    ::
+
+         # Resize the background of the text window if it's too small.         
+         init python:
+             style.window.background = Frame("frame.png", 10, 10)
+        """
+        
+    __version__ = 1
+
+    def after_upgrade(self, version):
+        if version < 2:
+            self.left = self.xborder
+            self.right = self.xborder
+            self.top = self.yborder
+            self.bottom = self.yborder
+
+    def __init__(self, image, left, top, right=None, bottom=None, bilinear=True, tile=False, **properties):
+        super(Frame, self).__init__(**properties)
+
+        self.image = renpy.easy.displayable(image)
+        self.tile = tile
+
+        if right is None:
+            right = left
+        if bottom is None:
+            bottom = top
+
+        self.left = left
+        self.top = top
+        self.right = right
+        self.bottom = bottom
+
+    def render(self, width, height, st, at):
+
+        crend = render(self.image, width, height, st, at)
+
+        sw, sh = crend.get_size()
+        sw = int(sw)
+        sh = int(sh)
+        
+        dw = int(width)
+        dh = int(height)
+        
+        bw = self.left + self.right
+        bh = self.top + self.bottom
+        
+        xborder = min(bw, sw - 2, dw)
+        if xborder:
+            left = self.left * xborder / bw
+            right = self.right * xborder / bw
+        else:
+            left = 0
+            right = 0
+            
+        yborder = min(bh, sh - 2, dh)
+        if yborder:
+            top = self.top * yborder / bh
+            bottom = self.bottom * yborder / bh
+        else:
+            top = 0
+            bottom = 0 
+
+        if renpy.display.draw.info["renderer"] == "sw":
+            return self.sw_render(crend, dw, dh, left, top, right, bottom)
+
+        def draw(x0, x1, y0, y1):
+
+            # Compute the coordinates of the left, right, top, and
+            # bottom sides of the region, for both the source and
+            # destination surfaces.
+
+            # left side.
+            if x0 >= 0:
+                dx0 = x0
+                sx0 = x0
+            else:
+                dx0 = dw + x0
+                sx0 = sw + x0
+
+            # right side.
+            if x1 > 0:
+                dx1 = x1
+                sx1 = x1
+            else:
+                dx1 = dw + x1
+                sx1 = sw + x1
+
+            # top side.
+            if y0 >= 0:
+                dy0 = y0
+                sy0 = y0
+            else:
+                dy0 = dh + y0
+                sy0 = sh + y0
+        
+            # bottom side
+            if y1 > 0:
+                dy1 = y1
+                sy1 = y1
+            else:
+                dy1 = dh + y1
+                sy1 = sh + y1
+
+            # Quick exit.
+            if sx0 == sx1 or sy0 == sy1:
+                return
+            
+            # Compute sizes.
+            csw = sx1 - sx0
+            csh = sy1 - sy0
+            cdw = dx1 - dx0
+            cdh = dy1 - dy0
+
+            if csw <= 0 or csh <= 0 or cdh <= 0 or cdw <= 0:
+                return
+            
+            # Get a subsurface.
+            cr = crend.subsurface((sx0, sy0, csw, csh))
+                        
+            # Scale or tile if we have to.
+            if csw != cdw or csh != cdh:
+
+                if self.tile:
+                    newcr = Render(cdw, cdh)
+                    newcr.clipping = True
+                    
+                    for x in range(0, cdw, csw):
+                        for y in range(0, cdh, csh):
+                            newcr.blit(cr, (x, y))
+
+                    cr = newcr
+                    
+                else:
+                    
+                    newcr = Render(cdw, cdh)
+                    newcr.forward = Matrix2D(1.0 * csw / cdw, 0, 0, 1.0 * csh / cdh)
+                    newcr.reverse = Matrix2D(1.0 * cdw / csw, 0, 0, 1.0 * cdh / csh)
+                    newcr.blit(cr, (0, 0))
+
+                    cr = newcr
+
+            # Blit.
+            rv.blit(cr, (dx0, dy0))
+            return
+        
+        rv = Render(dw, dh)
+
+        self.draw_pattern(draw, left, top, right, bottom)
+        
+        return rv
+
+    def draw_pattern(self, draw, left, top, right, bottom):
+        # Top row.
+        if top:
+
+            if left:
+                draw(0, left, 0, top)
+
+            draw(left, -right, 0, top)
+
+            if right:
+                draw(-right, 0, 0, top)
+
+        # Middle row.
+        if left:
+            draw(0, left, top, -bottom)
+
+        draw(left, -right, top, -bottom)
+
+        if right:
+            draw(-right, 0, top, -bottom)
+
+        # Bottom row.
+        if bottom:
+            if left:
+                draw(0, left, -bottom, 0)
+
+            draw(left, -right, -bottom, 0)
+
+            if right:
+                draw(-right, 0, -bottom, 0)
+
+        
+    
+    def sw_render(self, crend, dw, dh, left, top, right, bottom):
+
+        source = crend.render_to_texture(True)
+        sw, sh = source.get_size()
+
+        dest = renpy.display.swdraw.surface(dw, dh, True)
+        rv = dest
+
+        def draw(x0, x1, y0, y1):
+
+            # Compute the coordinates of the left, right, top, and
+            # bottom sides of the region, for both the source and
+            # destination surfaces.
+
+            # left side.
+            if x0 >= 0:
+                dx0 = x0
+                sx0 = x0
+            else:
+                dx0 = dw + x0
+                sx0 = sw + x0
+        
+            # right side.
+            if x1 > 0:
+                dx1 = x1
+                sx1 = x1
+            else:
+                dx1 = dw + x1
+                sx1 = sw + x1
+
+            # top side.
+            if y0 >= 0:
+                dy0 = y0
+                sy0 = y0
+            else:
+                dy0 = dh + y0
+                sy0 = sh + y0
+        
+            # bottom side
+            if y1 > 0:
+                dy1 = y1
+                sy1 = y1
+            else:
+                dy1 = dh + y1
+
+                sy1 = sh + y1
+
+            # Quick exit.
+            if sx0 == sx1 or sy0 == sy1 or dx1 <= dx0 or dy1 <= dy0:
+                return
+
+            # Compute sizes.
+            srcsize = (sx1 - sx0, sy1 - sy0)
+            dstsize = (int(dx1 - dx0), int(dy1 - dy0))
+            
+            # Get a subsurface.
+            surf = source.subsurface((sx0, sy0, srcsize[0], srcsize[1]))
+
+            # Scale or tile if we have to.
+            if dstsize != srcsize:
+                if self.tile:
+                    tilew, tileh = srcsize
+                    dstw, dsth = dstsize
+
+                    surf2 = renpy.display.pgrender.surface_unscaled(dstsize, surf)
+
+                    for y in range(0, dsth, tileh):
+                        for x in range(0, dstw, tilew):
+                            surf2.blit(surf, (x, y))
+
+                    surf = surf2 
+
+                else:
+                    surf2 = renpy.display.scale.real_transform_scale(surf, dstsize)
+                    surf = surf2
+                        
+            # Blit.
+            dest.blit(surf, (dx0, dy0))
+
+        self.draw_pattern(draw, left, top, right, bottom)
+
+        rrv = renpy.display.render.Render(dw, dh)
+        rrv.blit(rv, (0, 0))
+        rrv.depends_on(crend)
+                      
+        # And, finish up.
+        return rrv
+    
+    def visit(self):
+        return [ self.image ]
+
+
diff --git a/unrpyc/renpy/display/imagemap.py b/unrpyc/renpy/display/imagemap.py
new file mode 100644
index 0000000..380bf80
--- /dev/null
+++ b/unrpyc/renpy/display/imagemap.py
@@ -0,0 +1,233 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file handles imagemap caching.
+
+import pygame
+import renpy.display
+
+from renpy.display.render import render
+
+import hashlib
+import os
+
+# A list of cache images we've already written.
+cached = set()
+
+class ImageMapCrop(renpy.display.core.Displayable):
+    """
+    This handles the cropping of uncached imagemap components.
+    """
+
+    def __init__(self, child, rect):
+        super(ImageMapCrop, self).__init__()
+        
+        self.child = child
+        self.rect = rect
+
+    def visit(self):
+        return [ self.child ]
+        
+    def render(self, width, height, st, at):
+        cr = render(self.child, width, height, st, at)
+        return cr.subsurface(self.rect)
+    
+        
+class ImageCacheCrop(renpy.display.core.Displayable):
+    """
+    This handles the cropping of an imagemap component.
+    """
+
+    def __init__(self, cache, index):
+        super(ImageCacheCrop, self).__init__()
+        
+        # The cache object we're associated with. 
+        self.cache = cache
+
+        # The index of 
+        self.index = index
+
+    def visit(self):
+        return self.cache.visit(self.index)
+        
+    def render(self, width, height, st, at):
+        return self.cache.render(self.index, width, height, st, at)
+
+class ImageMapCache(renpy.object.Object):
+
+    def __init__(self, enable):
+        self.md5 = hashlib.md5()
+
+        # A list of (image, rect) tuples. The index in this list is used
+        # as a unique identifier for an ImageCacheCrop object.
+        self.imagerect = [ ]
+        
+        # A map from (image, rect) to ImageCacheCrop object.  
+        self.hotspots = { }
+
+        # A list of (width, height, index) tuples.
+        self.areas = [ ]
+
+        # The image containing our children.
+        self.cache = None
+
+        # A list that, for each hotspot, gives the rectangle in the cache
+        # image corresponding to that hotspot.
+        self.cache_rect = None
+
+        # The size of the cache.
+        self.cache_width = None
+        self.cache_height = None
+
+        self.enable = enable
+
+    def visit(self, index):
+        if self.cache is not None:
+            return [ self.cache ]
+        else:
+            return [ self.imagerect[index][0] ]
+        
+    def crop(self, d, rect):
+        if not isinstance(d, renpy.display.im.ImageBase) or \
+                not renpy.config.imagemap_cache or \
+                not self.enable:
+            return ImageMapCrop(d, rect)
+
+        key = (d, rect)
+        rv = self.hotspots.get(key, None)
+        if rv is not None:
+            return rv
+
+        self.md5.update(repr(d.identity))
+        self.md5.update(repr(d.identity))
+
+        index = len(self.imagerect)
+        rv = ImageCacheCrop(self, index)
+        
+        self.imagerect.append(key)
+        self.hotspots[key] = rv
+        self.areas.append((rect[2] + 2, rect[3] + 2, index))
+
+        return rv
+
+    def layout(self):
+        self.areas.sort()
+        self.areas.reverse()
+        self.cache_rect = [ None ] * len(self.areas)
+        
+        # The width of the cache image.
+        width = self.areas[0][0]
+
+        x = 0
+        y = 0
+        line_height = 0
+
+        for w, h, i in self.areas:
+
+            if x + w > width:
+                y += line_height
+                line_height = 0
+                x = 0
+
+            self.cache_rect[i] = (x+1, y+1, w-2, h-2)
+
+            x += w
+            if line_height < h:
+                line_height = h
+            
+        self.cache_width = width
+        self.cache_height = y + line_height
+
+    def write_cache(self, filename):
+        
+        if filename in cached:
+            return
+
+        cached.add(filename)
+
+        # If all of our dependencies are of the same age or less,
+        # we don't need to rebuild the cache.
+
+        if renpy.loader.loadable(filename):
+            d_set = set()
+            mtime = 0
+
+            for i, rect in self.imagerect:
+                if i in d_set:
+                    continue
+
+                d_set.add(i)
+                mtime = max(i.get_mtime(), mtime)
+
+            if renpy.loader.get_mtime(filename) >= mtime:
+                return
+        
+        fn = os.path.join(renpy.config.gamedir, filename)
+        dir = os.path.dirname(fn) #@ReservedAssignment
+        
+        if not os.path.exists(dir):
+            os.makedirs(dir)
+            
+        cache = pygame.Surface((self.cache_width, self.cache_height), pygame.SRCALPHA, 32)
+                    
+        for i, (d, rect) in enumerate(self.imagerect):
+            x, y, _w, _h = self.cache_rect[i]
+
+            surf = renpy.display.im.cache.get(d).subsurface(rect)
+            cache.blit(surf, (x, y))
+            
+        pygame.image.save(cache, renpy.exports.fsencode(fn))
+        
+    def finish(self):
+        if not self.areas:
+            return
+
+        filename = "im-%s.png" % (self.md5.hexdigest())
+
+        if renpy.game.preferences.language:
+            filename = renpy.game.preferences.language + "-" + filename
+
+        filename = "cache/" + filename
+
+        self.md5 = None
+
+        self.layout()
+
+        if renpy.config.developer:                                                                 
+            try:
+                self.write_cache(filename)
+            except:
+                pass
+                
+        if renpy.loader.loadable(filename):
+            self.cache = renpy.display.im.Image(filename)
+
+
+    def render(self, index, width, height, st, at):
+        if self.cache is None:
+            d, rect = self.imagerect[index]
+            return render(d, width, height, st, at).subsurface(rect)
+
+        return render(self.cache, width, height, st, at).subsurface(self.cache_rect[index])
+    
+            
+        
+        
diff --git a/unrpyc/renpy/display/joystick.py b/unrpyc/renpy/display/joystick.py
new file mode 100644
index 0000000..c606dd9
--- /dev/null
+++ b/unrpyc/renpy/display/joystick.py
@@ -0,0 +1,126 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file is responsible for joystick support in Ren'Py.
+
+import os
+import pygame
+
+import renpy.display
+
+# Do we have a joystick enabled?
+enabled = False
+
+# The old states for each axis.
+old_axis_states = { }
+
+def init():
+    """
+    Initialize the joystick system.
+    """
+
+    global enabled
+
+    if not renpy.config.joystick:
+        return
+
+    if 'RENPY_DISABLE_JOYSTICK' in os.environ:
+        return
+
+    try:
+        pygame.joystick.init()
+        
+        for i in range(0, pygame.joystick.get_count()):
+            pygame.joystick.Joystick(i).init()
+            enabled = True
+    except:        
+        if renpy.config.debug:
+            raise
+        
+def event(ev):
+
+    if not enabled:
+        return ev
+
+    if ev.type == pygame.JOYAXISMOTION:
+
+        if not renpy.display.interface.focused:
+            return None
+
+        if ev.value >= 0.5:
+            state = "Positive"
+        elif ev.value <= -0.5:
+            state = "Negative"
+        else:
+            state = None
+
+        oldstate = old_axis_states.get((ev.joy, ev.axis), None)
+
+        if state == oldstate:
+            return None
+
+        if oldstate:
+            release = "Axis %d.%d %s" % (ev.joy, ev.axis, oldstate)
+        else:
+            release = None
+
+        old_axis_states[ev.joy, ev.axis] = state
+
+        if state:
+            press = "Axis %d.%d %s" % (ev.joy, ev.axis, state)
+        else:
+            press = None
+
+        if not press and not release:
+            return None
+
+        return pygame.event.Event(renpy.display.core.JOYEVENT,
+                                  press=press, release=release)
+
+    if ev.type == pygame.JOYBUTTONDOWN:
+        
+        if not renpy.display.interface.focused:
+            return None
+
+        return pygame.event.Event(renpy.display.core.JOYEVENT,
+                                  press="Button %d.%d" % (ev.joy, ev.button),
+                                  release=None)
+    if ev.type == pygame.JOYBUTTONUP:
+
+        if not renpy.display.interface.focused:
+            return None
+
+        return pygame.event.Event(renpy.display.core.JOYEVENT,
+                                  press=None,
+                                  release="Button %d.%d" % (ev.joy, ev.button))
+
+    return ev
+
+class JoyBehavior(renpy.display.layout.Null):
+    """
+    This is a behavior intended for joystick calibration. If a joystick
+    event occurs, this returns it as a string.
+    """
+
+    def event(self, ev, x, y, st):
+        if ev.type == renpy.display.core.JOYEVENT:
+            return ev.press
+        
diff --git a/unrpyc/renpy/display/layout.py b/unrpyc/renpy/display/layout.py
new file mode 100644
index 0000000..e07e28d
--- /dev/null
+++ b/unrpyc/renpy/display/layout.py
@@ -0,0 +1,1744 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains classes that handle layout of displayables on
+# the screen.
+
+from renpy.display.render import render, Render
+import renpy.display
+
+
+def scale(num, base):
+    """
+    If num is a float, multiplies it by base and returns that. Otherwise,
+    returns num unchanged.
+    """
+
+    if isinstance(num, float):
+        return num * base
+    else:
+        return num
+
+class Null(renpy.display.core.Displayable):
+    """
+    :doc: disp_imagelike
+        
+    A displayable that creates an empty box on the screen. The size
+    of the box is controlled by `width` and `height`. This can be used
+    when a displayable requires a child, but no child is suitable, or
+    as a spacer inside a box.
+
+    ::
+
+        image logo spaced = HBox("logo.png", Null(width=100), "logo.png")
+
+    """
+
+    def __init__(self, width=0, height=0, **properties):
+        super(Null, self).__init__(**properties)
+        self.width = width
+        self.height = height
+
+    def render(self, width, height, st, at):
+        rv = renpy.display.render.Render(self.width, self.height)
+
+        if self.focusable:
+            rv.add_focus(self, None, None, None, None, None)
+
+        return rv
+
+
+class Container(renpy.display.core.Displayable):
+    """
+    This is the base class for containers that can have one or more
+    children.
+
+    @ivar children: A list giving the children that have been added to
+    this container, in the order that they were added in.
+
+    @ivar child: The last child added to this container. This is also
+    used to access the sole child in containers that can only hold
+    one child.
+
+    @ivar offsets: A list giving offsets for each of our children.
+    It's expected that render will set this up each time it is called.
+
+    @ivar sizes: A list giving sizes for each of our children. It's
+    also expected that render will set this each time it is called.
+
+    """
+
+    # We indirect all list creation through this, so that we can
+    # use RevertableLists if we want.
+    _list_type = list
+
+    def __init__(self, *args, **properties):
+
+        self.children = self._list_type()
+        self.child = None
+        self.offsets = self._list_type()
+
+        for i in args:
+            self.add(i)
+
+        super(Container, self).__init__(**properties)
+
+    def set_style_prefix(self, prefix, root):
+        super(Container, self).set_style_prefix(prefix, root)
+
+        for i in self.children:
+            i.set_style_prefix(prefix, False)
+            
+    def add(self, d):
+        """
+        Adds a child to this container.
+        """
+
+        child = renpy.easy.displayable(d)
+        
+        self.children.append(child)
+       
+        self.child = child
+        self.offsets = self._list_type()
+
+    def remove(self, d):
+        """
+        Removes the first instance of child from this container. May
+        not work with all containers.
+        """
+
+        for i, c in enumerate(self.children):
+            if c is d:
+                break
+        else:
+            return
+
+        self.children.pop(i) # W0631
+        self.offsets = self._list_type()
+
+        if self.children:
+            self.child = self.children[-1]
+        else:
+            self.child = None
+        
+        
+    def update(self):
+        """
+        This should be called if a child is added to this
+        displayable outside of the render function.
+        """
+
+        renpy.display.render.invalidate(self)
+
+        
+    def render(self, width, height, st, at):
+
+        rv = Render(width, height)
+        self.offsets = self._list_type()
+        
+        for c in self.children:
+            cr = render(c, width, height, st, at)
+            offset = c.place(rv, 0, 0, width, height, cr)
+            self.offsets.append(offset)
+
+        return rv
+
+    
+    def event(self, ev, x, y, st):
+
+        children = self.children
+        offsets = self.offsets
+        
+        for i in range(len(offsets) - 1, -1, -1):
+
+            d = children[i]
+            xo, yo = offsets[i]
+
+            rv = d.event(ev, x - xo, y - yo, st)    
+            if rv is not None:
+                return rv
+                
+        return None
+
+    def visit(self):
+        return self.children
+    
+    # These interact with the ui functions to allow use as a context
+    # manager.
+
+    def __enter__(self):
+
+        renpy.ui.context_enter(self)
+        return self
+
+    def __exit__(self, exc_type, exc_val, exc_tb):
+
+        renpy.ui.context_exit(self)
+        return False
+        
+        
+    
+
+def LiveComposite(size, *args, **properties):
+    """
+    :args: disp_imagelike
+    
+    This creates a new displayable of `size`, by compositing other
+    displayables. `size` is a (width, height) tuple.
+
+    The remaining positional arguments are used to place images inside
+    the LiveComposite. The remaining positional arguments should come
+    in groups of two, with the first member of each group an (x, y)
+    tuple, and the second member of a group is a displayable that
+    is composited at that position.
+
+    Displayables are composited from back to front.
+    
+    ::
+
+       image eileen composite = LiveComposite(
+           (300, 600),
+           (0, 0), "body.png",
+           (0, 0), "clothes.png",
+           (50, 50), "expression.png")
+    """
+
+    properties.setdefault('style', 'image_placement')
+
+    width, height = size
+
+    rv = Fixed(xmaximum=width, ymaximum=height, xminimum=width, yminimum=height, **properties)
+
+    if len(args) % 2 != 0:
+        raise Exception("LiveComposite requires an odd number of arguments.")
+
+    for pos, widget in zip(args[0::2], args[1::2]):
+        xpos, ypos = pos
+        rv.add(Position(renpy.easy.displayable(widget),
+                        xpos=xpos, xanchor=0, ypos=ypos, yanchor=0))
+
+    return rv
+
+class Position(Container):
+    """
+    Controls the placement of a displayable on the screen, using
+    supplied position properties. This is the non-curried form of
+    Position, which should be used when the user has directly created
+    the displayable that will be shown on the screen.
+    """
+
+    def __init__(self, child, style='image_placement', **properties):
+        """
+        @param child: The child that is being laid out.
+
+        @param style: The base style of this position.
+
+        @param properties: Position properties that control where the
+        child of this widget is placed.
+        """
+
+        super(Position, self).__init__(style=style, **properties)
+        self.add(child)
+
+    def render(self, width, height, st, at):
+
+        surf = render(self.child, width, height, st, at)
+
+        self.offsets = [ (0, 0) ]
+
+        rv = renpy.display.render.Render(surf.width, surf.height)
+        rv.blit(surf, (0, 0))
+        
+        return rv
+
+    def get_placement(self):
+    
+        xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = self.child.get_placement()
+
+        v = self.style.xpos
+        if v is not None:
+            xpos = v
+
+        v = self.style.ypos
+        if v is not None:
+            ypos = v
+
+        v = self.style.xanchor
+        if v is not None:
+            xanchor = v
+
+        v = self.style.yanchor
+        if v is not None:
+            yanchor = v
+
+        v = self.style.xoffset
+        if v is not None:
+            xoffset = v
+
+        v = self.style.yoffset
+        if v is not None:
+            yoffset = v
+
+        v = self.style.subpixel
+        if v is not None:
+            subpixel = v
+
+        return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+
+    
+class Grid(Container):
+    """
+    A grid is a widget that evenly allocates space to its children.
+    The child widgets should not be greedy, but should instead be
+    widgets that only use part of the space available to them.
+    """
+
+    def __init__(self, cols, rows, padding=None,
+                 transpose=False,
+                 style='grid', **properties):
+        """
+        @param cols: The number of columns in this widget.
+
+        @params rows: The number of rows in this widget.
+
+        @params transpose: True if the grid should be transposed.
+        """
+
+        if padding is not None:
+            properties.setdefault('spacing', padding)
+        
+        super(Grid, self).__init__(style=style, **properties)
+
+        cols = int(cols)
+        rows = int(rows)
+        
+        self.cols = cols
+        self.rows = rows
+
+        self.transpose = transpose
+
+    def render(self, width, height, st, at):
+
+        # For convenience and speed.
+        padding = self.style.spacing
+        cols = self.cols
+        rows = self.rows
+
+        if len(self.children) != cols * rows:            
+            if len(self.children) < cols * rows:
+                raise Exception("Grid not completely full.")
+            else:
+                raise Exception("Grid overfull.")
+            
+        # If necessary, transpose the grid (kinda hacky, but it works here.)
+        if self.transpose:
+            self.transpose = False
+
+            old_children = self.children[:]
+            
+            for y in range(0, rows):
+                for x in range(0, cols):
+                    self.children[x + y * cols] = old_children[ y + x * rows ]
+
+            
+        # Now, start the actual rendering.
+
+        renwidth = width
+        renheight = height
+
+        if self.style.xfill:
+            renwidth = (width - (cols - 1) * padding) / cols
+        if self.style.yfill:
+            renheight = (height - (rows - 1) * padding) / rows
+        
+        renders = [ render(i, renwidth, renheight, st, at) for i in self.children ]
+        sizes = [ i.get_size() for i in renders ]
+
+        cwidth = 0
+        cheight = 0
+
+        for w, h in sizes:
+            cwidth = max(cwidth, w)
+            cheight = max(cheight, h)
+
+        if self.style.xfill:
+            cwidth = renwidth
+
+        if self.style.yfill:
+            cheight = renheight
+
+        width = cwidth * cols + padding * (cols - 1)
+        height = cheight * rows + padding * (rows - 1)
+
+        rv = renpy.display.render.Render(width, height)
+
+        self.offsets = [ ]
+            
+        for y in range(0, rows):
+            for x in range(0, cols):
+
+                child = self.children[ x + y * cols ]
+                surf = renders[x + y * cols]
+
+                xpos = x * (cwidth + padding)
+                ypos = y * (cheight + padding)
+
+                offset = child.place(rv, xpos, ypos, cwidth, cheight, surf)
+                self.offsets.append(offset)
+
+        return rv
+
+class IgnoreLayers(Exception):
+    """
+    Raise this to have the event ignored by layers, but reach the
+    underlay.
+    """
+
+    pass
+    
+class MultiBox(Container):
+
+    layer_name = None
+    first = True
+    order_reverse = False
+    
+    def __init__(self, spacing=None, layout=None, style='default', **properties):
+
+        if spacing is not None:
+            properties['spacing'] = spacing
+
+        super(MultiBox, self).__init__(style=style, **properties)
+
+        self.default_layout = layout
+
+        # The start and animation times for children of this
+        # box.
+        self.start_times = [ ]
+        self.anim_times = [ ]
+
+        # A map from layer name to the widget corresponding to
+        # that layer.
+        self.layers = None
+
+        # The scene list for this widget.
+        self.scene_list = None
+
+    def add(self, widget, start_time=None, anim_time=None): # W0221
+        super(MultiBox, self).add(widget)
+        self.start_times.append(start_time)
+        self.anim_times.append(anim_time)
+        
+    def append_scene_list(self, l):
+
+        for sle in l:
+            self.add(sle.displayable, sle.show_time, sle.animation_time)
+
+        if self.scene_list is None:
+            self.scene_list = [ ]
+            
+        self.scene_list.extend(l)
+        
+    def render(self, width, height, st, at):
+
+        # Do we need to adjust the child times due to our being a layer?
+        if self.layer_name or (self.layers is not None):
+            adjust_times = True
+        else:
+            adjust_times = False
+
+        xminimum = self.style.xminimum
+        if xminimum is not None:
+            width = max(width, scale(xminimum, width))
+
+        yminimum = self.style.yminimum
+        if yminimum is not None:
+            height = max(height, scale(yminimum, height))
+            
+        if self.first:
+
+            self.first = False
+
+            if adjust_times:
+            
+                it = renpy.game.interface.interact_time
+            
+                self.start_times = [ i or it for i in self.start_times ]
+                self.anim_times = [ i or it for i in self.anim_times ]
+                            
+            layout = self.style.box_layout
+
+            if layout is None:
+                layout = self.default_layout
+
+            self.layout = layout # W0201
+
+        else:
+            layout = self.layout
+
+
+        # Handle time adjustment, store the results in csts and cats.
+        if adjust_times:            
+            t = renpy.game.interface.frame_time
+
+            csts = [ t - start for start in self.start_times ]
+            cats = [ t - anim for anim in self.anim_times ]
+
+        else:
+            csts = [ st ] * len(self.children)
+            cats = [ at ] * len(self.children)
+
+        offsets = [ ]
+
+        if layout == "fixed":
+
+            rv = None
+
+            if self.style.order_reverse:
+                iterator = list(zip(reversed(self.children), reversed(csts), reversed(cats)))
+            else:
+                iterator = list(zip(self.children, csts, cats))
+                        
+            for child, cst, cat in iterator:
+                
+                surf = render(child, width, height, cst, cat)
+                                
+                if rv is None:
+
+                    if self.style.fit_first:
+                        sw, sh = surf.get_size()
+                        width = min(width, sw)
+                        height = min(height, sh)
+
+                        
+                    rv = renpy.display.render.Render(width, height, layer_name=self.layer_name)                    
+
+                if surf:
+                    offset = child.place(rv, 0, 0, width, height, surf)
+                    offsets.append(offset)
+                else:
+                    offsets.append((0, 0))
+
+            if rv is None:
+                rv = renpy.display.render.Render(width, height, layer_name=self.layer_name)                                        
+
+            if self.style.order_reverse:
+                offsets.reverse()
+                
+            self.offsets = offsets
+
+            return rv
+
+        # If we're here, we have a box, either horizontal or vertical. Which is good,
+        # as we can share some code between boxes.
+ 
+            
+        spacing = self.style.spacing
+        first_spacing = self.style.first_spacing
+
+        if first_spacing is None:
+            first_spacing = spacing
+
+        spacings = [ first_spacing ] + [ spacing ] * (len(self.children) - 1)
+                    
+        box_wrap = self.style.box_wrap
+             
+        xfill = self.style.xfill
+        yfill = self.style.yfill
+                    
+        # The shared height and width of the current line. The line_height must
+        # be 0 for a vertical box, and the line_width must be 0 for a horizontal
+        # box.
+        line_width = 0
+        line_height = 0
+        
+        # The children to layout.
+        children = list(self.children)
+        if self.style.box_reverse:
+            children.reverse()
+            spacings.reverse()
+        
+        # a list of (child, x, y, w, h, surf) tuples that are turned into 
+        # calls to child.place().
+        placements = [ ] 
+                
+        # The maximum x and y.
+        maxx = 0
+        maxy = 0
+                
+        def layout_line(line, xfill, yfill):
+            """
+            Lays out a single line. 
+            
+            `line` a list of (child, x, y, surf) tuples. 
+            `xfill` the amount of space to add in the x direction.
+            `yfill` the amount of space to add in the y direction.
+            """
+            
+            xfill = max(0, xfill)
+            yfill = max(0, yfill)
+            
+            if line:            
+                xperchild = xfill / len(line)
+                yperchild = yfill / len(line)
+            else:
+                xperchild = 0
+                yperchild = 0
+            
+            maxxout = maxx
+            maxyout = maxy
+            
+            for i, (child, x, y, surf) in enumerate(line):
+                sw, sh = surf.get_size()
+                sw = max(line_width, sw)
+                sh = max(line_height, sh)
+
+                x += i * xperchild
+                y += i * yperchild
+
+                sw += xperchild
+                sh += yperchild
+                
+                placements.append((child, x, y, sw, sh, surf))
+            
+                maxxout = max(maxxout, x + sw)
+                maxyout = max(maxyout, y + sh)
+                
+            return maxxout, maxyout
+            
+        x = 0
+        y = 0              
+              
+        full_width = False
+        full_height = False
+                    
+        if layout == "horizontal":
+
+            full_height = yfill
+
+            line_height = 0
+            line = [ ]
+            remwidth = width
+
+            for d, padding, cst, cat in zip(children, spacings, csts, cats):
+                
+                if box_wrap:                    
+                    rw = width
+                else:
+                    rw = remwidth
+                
+                surf = render(d, rw, height - y, cst, cat)
+                sw, sh = surf.get_size()
+
+                if box_wrap and remwidth - sw - padding <= 0 and line:
+                    maxx, maxy = layout_line(line, remwidth if xfill else 0, 0)                        
+                        
+                    y += line_height
+                    x = 0
+                    line_height = 0
+                    remwidth = width
+                    line = [ ]
+                    
+
+                line.append((d, x, y, surf))
+                line_height = max(line_height, sh)                
+                x += sw + padding
+                remwidth -= (sw + padding)
+                
+            maxx, maxy = layout_line(line, remwidth if xfill else 0, 0)
+            
+                  
+        elif layout == "vertical":
+
+            full_width = xfill
+
+            line_width = 0
+            line = [ ]
+            remheight = height
+
+            for d, padding, cst, cat in zip(children, spacings, csts, cats):
+                
+                if box_wrap:                    
+                    rh = height
+                else:
+                    rh = remheight
+                
+                surf = render(d, width - x, rh, cst, cat)
+                sw, sh = surf.get_size()
+
+                if box_wrap and remheight - sh - padding <= 0:
+                    maxx, maxy = layout_line(line, 0, remheight if yfill else 0)                        
+                        
+                    x += line_width
+                    y = 0
+                    line_width = 0
+                    remheight = height
+                    line = [ ]
+                    
+                line.append((d, x, y, surf))
+                line_width = max(line_width, sw)                
+                y += sh + padding
+                remheight -= (sh + padding)
+                
+            maxx, maxy = layout_line(line, 0, remheight if yfill else 0)
+
+        # Back to the common for vertical and horizontal.
+
+        if not xfill:
+            width = maxx
+        
+        if not yfill:
+            height = maxy
+                    
+        rv = renpy.display.render.Render(width, height)
+
+        if self.style.box_reverse ^ self.style.order_reverse:
+            placements.reverse()
+
+        for child, x, y, w, h, surf in placements:
+            if full_width:
+                w = width
+            if full_height:
+                h = height
+                            
+            offset = child.place(rv, x, y, w, h, surf)
+            offsets.append(offset)
+            
+        if self.style.order_reverse:
+            offsets.reverse()
+            
+        self.offsets = offsets
+            
+        return rv
+
+        
+    def event(self, ev, x, y, st):
+        
+        
+        children_offsets = list(zip(self.children, self.offsets, self.start_times))
+
+        if not self.style.order_reverse:
+            children_offsets.reverse()
+        
+        try:
+        
+            for i, (xo, yo), t in children_offsets: 
+
+                if t is None:
+                    cst = st
+                else:
+                    cst = renpy.game.interface.event_time - t
+
+                rv = i.event(ev, x - xo, y - yo, cst)    
+                if rv is not None:
+                    return rv
+
+        except IgnoreLayers:
+            if self.layers:
+                return None
+            else:
+                raise
+                
+        return None
+
+def Fixed(**properties):
+    return MultiBox(layout='fixed', **properties)
+
+class SizeGroup(renpy.object.Object):
+
+    def __init__(self):
+
+        super(SizeGroup, self).__init__()
+        
+        self.members = [ ]
+        self._width = None
+        self.computing_width = False
+        
+    def width(self, width, height, st, at):
+        if self._width is not None:
+            return self._width
+
+        if self.computing_width:
+            return 0
+
+        self.computing_width = True
+        
+        maxwidth = 0
+
+        for i in self.members:
+            rend = renpy.display.render.render(i, width, height, st, at)
+            maxwidth = max(rend.width, maxwidth)
+            renpy.display.render.invalidate(i)
+            
+        self._width = maxwidth
+        self.computing_width = False
+
+        return maxwidth
+        
+
+size_groups = dict()
+    
+class Window(Container):
+    """
+    A window that has padding and margins, and can place a background
+    behind its child. `child` is the child added to this
+    displayable. All other properties are as for the :ref:`Window`
+    screen language statement.
+    """
+    
+    def __init__(self, child, style='window', **properties):
+
+        super(Window, self).__init__(style=style, **properties)
+        if child is not None:
+            self.add(child)
+
+    def visit(self):
+        return [ self.style.background ] + self.children
+
+    def get_child(self):
+        return self.style.child or self.child
+
+    def per_interact(self):
+        size_group = self.style.size_group
+
+        if size_group:
+            group = size_groups.get(size_group, None)
+            if group is None:
+                group = size_groups[size_group] = SizeGroup()
+
+            group.members.append(self)
+
+    def predict_one(self):
+        # Child will be predicted by visiting.
+
+        pd = renpy.display.predict.displayable
+        style = self.style
+
+        pd(style.insensitive_background)
+        pd(style.idle_background)
+        pd(style.hover_background)
+        pd(style.selected_idle_background)
+        pd(style.selected_hover_background)
+
+        pd(style.insensitive_child)
+        pd(style.idle_child)
+        pd(style.hover_child)
+        pd(style.selected_idle_child)
+        pd(style.selected_hover_child)
+
+        pd(style.insensitive_foreground)
+        pd(style.idle_foreground)
+        pd(style.hover_foreground)
+        pd(style.selected_idle_foreground)
+        pd(style.selected_hover_foreground)
+            
+    def render(self, width, height, st, at):
+
+        # save some typing.
+        style = self.style
+
+        xminimum = scale(style.xminimum, width)
+        yminimum = scale(style.yminimum, height)
+
+        size_group = self.style.size_group
+        if size_group and size_group in size_groups:
+            xminimum = max(xminimum, size_groups[size_group].width(width, height, st, at))
+        
+        left_margin = scale(style.left_margin, width)
+        left_padding = scale(style.left_padding, width)
+
+        right_margin = scale(style.right_margin, width)
+        right_padding = scale(style.right_padding, width)
+
+        top_margin = scale(style.top_margin, height)
+        top_padding = scale(style.top_padding, height)
+
+        bottom_margin = scale(style.bottom_margin, height)
+        bottom_padding = scale(style.bottom_padding, height)
+
+        # c for combined.
+        cxmargin = left_margin + right_margin
+        cymargin = top_margin + bottom_margin
+
+        cxpadding = left_padding + right_padding
+        cypadding = top_padding + bottom_padding
+
+        child = self.get_child()
+
+        # Render the child.
+        surf = render(child,
+                      width  - cxmargin - cxpadding,
+                      height - cymargin - cypadding,
+                      st, at)
+
+        sw, sh = surf.get_size()
+
+        # If we don't fill, shrink our size to fit.
+
+        if not style.xfill:
+            width = max(cxmargin + cxpadding + sw, xminimum)
+
+        if not style.yfill:
+            height = max(cymargin + cypadding + sh, yminimum)
+
+        rv = renpy.display.render.Render(width, height)
+
+        # Draw the background. The background should render at exactly the
+        # requested size. (That is, be a Frame or a Solid).
+        if style.background:
+            bw = width - cxmargin
+            bh = height - cymargin
+
+            back = render(style.background, bw, bh, st, at)
+
+            style.background.place(rv, left_margin, top_margin, bw, bh, back, main=False)
+
+        offsets = child.place(rv,
+                              left_margin + left_padding, 
+                              top_margin + top_padding,
+                              width  - cxmargin - cxpadding,
+                              height - cymargin - cypadding,
+                              surf)
+
+        # Draw the foreground. The background should render at exactly the
+        # requested size. (That is, be a Frame or a Solid).
+        if style.foreground:
+            bw = width  - cxmargin
+            bh = height - cymargin
+
+            back = render(style.foreground, bw, bh, st, at)
+
+            style.foreground.place(rv, left_margin, top_margin, bw, bh, back, main=False)
+
+        self.offsets = [ offsets ]
+
+        self.window_size = width, height # W0201
+
+        return rv
+
+
+def dynamic_displayable_compat(st, at, expr):
+    child = renpy.python.py_eval(expr)
+    return child, None
+
+class DynamicDisplayable(renpy.display.core.Displayable):
+    """
+    :doc: disp_dynamic
+
+    A displayable that can change its child based on a Python
+    function, over the course of an interaction.
+
+    `function`
+        A function that is called with the arguments:
+
+        * The amount of time the displayable has been shown for.
+        * The amount of time any displayable with the same tag has been shown for.
+        * Any positional or keyword arguments supplied to DynamicDisplayable.
+
+        and should return a (d, redraw) tuple, where:
+
+        * `d` is a displayable to show.
+        * `redraw` is the amount of time to wait before calling the
+          function again, or None to not call the function again
+          before the start of the next interaction.
+
+        `function` is called at the start of every interaction.
+
+    As a special case, `function` may also be a python string that evaluates
+    to a displayable. In that case, function is run once per interaction.
+
+    ::
+
+        # If tooltip is not empty, shows it in a text. Otherwise,
+        # show Null. Checks every tenth of a second to see if the
+        # tooltip has been updated.
+        init python:
+             def show_tooltip(st, at):
+                 if tooltip:
+                     return tooltip, .1
+                 else:
+                     return Null()
+
+        image tooltipper = DynamicDisplayable(show_tooltip)
+
+    """
+    
+    nosave = [ 'child' ]
+
+    def after_setstate(self):
+        self.child = None
+
+    def __init__(self, function, *args, **kwargs):
+
+        super(DynamicDisplayable, self).__init__()
+        self.child = None
+
+        if isinstance(function, str):
+            args = ( function, )
+            kwargs = { }
+            function = dynamic_displayable_compat
+
+        self.predict_function = kwargs.pop("_predict_function", None)            
+        self.function = function
+        self.args = args
+        self.kwargs = kwargs
+        self.st = 0
+        self.at = 0
+        
+    def visit(self):
+        return [ ]
+
+    def per_interact(self):
+        child, _ = self.function(self.st, self.at, *self.args, **self.kwargs)
+        child = renpy.easy.displayable(child)        
+        child.visit_all(lambda a : a.per_interact())
+
+        if child is not self.child:
+            renpy.display.render.redraw(self, 0)
+            self.child = child
+                    
+    def render(self, w, h, st, at):
+
+        self.st = st
+        self.at = at
+        
+        child, redraw = self.function(st, at, *self.args, **self.kwargs)
+        child = renpy.easy.displayable(child)
+        child.visit_all(lambda c : c.per_interact())
+
+        self.child = child
+
+        if redraw is not None:
+            renpy.display.render.redraw(self, redraw)
+        
+        return renpy.display.render.render(self.child, w, h, st, at)
+
+    def predict_one(self):
+        if not self.predict_function:
+            return
+        
+        for i in self.predict_function(*self.args, **self.kwargs):
+            if i is not None:
+                renpy.display.predict.displayable(i)
+        
+    def get_placement(self):
+        if not self.child:
+            self.per_interact()
+
+        return self.child.get_placement()
+
+    
+    def event(self, ev, x, y, st):
+        if self.child:
+            return self.child.event(ev, x, y, st)
+
+# This chooses the first member of switch that's being shown on the
+# given layer.
+def condition_switch_pick(switch):
+    for cond, d in switch:
+        if cond is None or renpy.python.py_eval(cond):
+            return d
+
+    raise Exception("Switch could not choose a displayable.")
+
+def condition_switch_show(st, at, switch):
+    return condition_switch_pick(switch), None
+
+def condition_switch_predict(switch):
+
+    if renpy.game.lint:
+        return [ d for _cond, d in switch ]
+
+    return [ condition_switch_pick(switch) ]
+
+def ConditionSwitch(*args, **kwargs):
+    """
+    :doc: disp_dynamic
+
+    This is a displayable that changes what it is showing based on
+    python conditions. The positional argument should be given in
+    groups of two, where each group consists of:
+
+    * A string containing a python condition.
+    * A displayable to use if the condition is true.
+
+    The first true condition has its displayable shown, at least
+    one condition should always be true.
+
+    ::
+
+        image jill = ConditionSwitch(
+            "jill_beers > 4", "jill_drunk.png",
+            "True", "jill_sober.png")
+    """
+        
+    kwargs.setdefault('style', 'default')
+    
+    switch = [ ]
+    
+    if len(args) % 2 != 0:
+        raise Exception('ConditionSwitch takes an even number of arguments')
+
+    for cond, d in zip(args[0::2], args[1::2]):
+
+        d = renpy.easy.displayable(d)
+        switch.append((cond, d))
+
+    rv = DynamicDisplayable(condition_switch_show,
+                            switch,
+                            _predict_function=condition_switch_predict)
+                              
+    return Position(rv, **kwargs)
+
+    
+def ShowingSwitch(*args, **kwargs):
+    """
+    :doc: disp_dynamic
+
+    This is a displayable that changes what it is showing based on the
+    images are showing on the screen. The positional argument should
+    be given in groups of two, where each group consists of:
+
+    * A string giving an image name, or None to indicate the default.
+    * A displayable to use if the condition is true.
+
+    A default image should be specified.
+
+    One use of ShowingSwitch is to have side images change depending on
+    the current emotion of a character. For example::
+
+       define e = Character("Eileen",
+           show_side_image=ShowingSwitch(
+               "eileen happy", Image("eileen_happy_side.png", xalign=1.0, yalign=1.0),
+               "eileen vhappy", Image("eileen_vhappy_side.png", xalign=1.0, yalign=1.0),
+               None, Image("eileen_happy_default.png", xalign=1.0, yalign=1.0),
+               )
+           )
+    """
+    
+    layer = kwargs.pop('layer', 'master')
+    
+    if len(args) % 2 != 0:
+        raise Exception('ShowingSwitch takes an even number of positional arguments')
+
+    condargs = [ ]
+
+    
+    for name, d in zip(args[0::2], args[1::2]):
+        if name is not None:
+            if not isinstance(name, tuple):        
+                name = tuple(name.split())
+            cond = "renpy.showing(%r, layer=%r)" % (name, layer)
+        else:
+            cond = None 
+            
+ 
+        condargs.append(cond)
+        condargs.append(d)
+
+    return ConditionSwitch(*condargs, **kwargs)
+
+
+class IgnoresEvents(Container):
+
+    def __init__(self, child, **properties):
+        super(IgnoresEvents, self).__init__(**properties)
+        self.add(child)
+    
+    def render(self, w, h, st, at):
+        cr = renpy.display.render.render(self.child, w, h, st, at)
+        cw, ch = cr.get_size()
+        rv = renpy.display.render.Render(cw, ch)
+        rv.blit(cr, (0, 0), focus=False)
+
+        return rv
+
+    def get_placement(self):
+        return self.child.get_placement()
+
+    # Ignores events.
+    def event(self, ev, x, y, st):
+        return None
+
+def edgescroll_proportional(n):
+    """
+    An edgescroll function that causes the move speed to be proportional
+    from the edge distance.
+    """
+    return n
+
+class Viewport(Container):
+
+    __version__ = 3
+
+    def after_upgrade(self, version):
+        if version < 1:
+            self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
+            self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
+            self.set_adjustments = False
+            self.mousewheel = False
+            self.draggable = False
+            self.width = 0
+            self.height = 0
+            
+        if version < 2:
+            self.drag_position = None
+            
+        if version < 3:
+            self.edge_size = False
+            self.edge_speed = False
+            self.edge_function = None
+            self.edge_xspeed = 0
+            self.edge_yspeed = 0
+            self.edge_last_st = None
+            
+    def __init__(self,
+                 child=None,
+                 child_size=(None, None),
+                 offsets=(None, None),
+                 xadjustment=None,
+                 yadjustment=None,
+                 set_adjustments=True,
+                 mousewheel=False,
+                 draggable=False,
+                 edgescroll=None,
+                 style='viewport',
+                 xinitial=None,
+                 yinitial=None,
+                 replaces=None,
+                 **properties):
+
+        super(Viewport, self).__init__(style=style, **properties)
+        if child is not None:
+            self.add(child)
+
+        if xadjustment is None:
+            self.xadjustment = renpy.display.behavior.Adjustment(1, 0)
+        else:
+            self.xadjustment = xadjustment
+            
+        if yadjustment is None:
+            self.yadjustment = renpy.display.behavior.Adjustment(1, 0)
+        else:
+            self.yadjustment = yadjustment
+            
+            
+        if isinstance(replaces, Viewport):
+            self.xadjustment.range = replaces.xadjustment.range
+            self.yadjustment.range = replaces.yadjustment.range
+            self.xadjustment.value = replaces.xadjustment.value
+            self.yadjustment.value = replaces.yadjustment.value
+            self.xoffset = replaces.xoffset
+            self.yoffset = replaces.yoffset
+            self.drag_position = replaces.drag_position
+        else:
+            self.xoffset = offsets[0] if (offsets[0] is not None) else xinitial
+            self.yoffset = offsets[1] if (offsets[1] is not None) else yinitial
+            self.drag_position = None
+
+        if self.xadjustment.adjustable is None:
+            self.xadjustment.adjustable = True
+
+        if self.yadjustment.adjustable is None:
+            self.yadjustment.adjustable = True
+
+        self.set_adjustments = set_adjustments
+        
+        self.child_width, self.child_height = child_size
+
+        self.mousewheel = mousewheel
+        self.draggable = draggable
+
+        self.width = 0
+        self.height = 0
+
+        # The speed at which we scroll in the x and y directions, in pixels
+        # per second.
+        self.edge_xspeed = 0
+        self.edge_yspeed = 0
+
+        # The last time we edgescrolled.
+        self.edge_last_st = None
+
+        if edgescroll is not None:
+
+            # The size of the edges that trigger scrolling.
+            self.edge_size = edgescroll[0]
+    
+            # How far from the edge we can scroll.
+            self.edge_speed = edgescroll[1]
+            
+            if len(edgescroll) >= 3:
+                self.edge_function = edgescroll[2]
+            else:
+                self.edge_function = edgescroll_proportional
+        
+        else:
+            self.edge_size = 0
+            self.edge_speed = 0
+            self.edge_function = edgescroll_proportional
+
+        
+    def per_interact(self):
+        self.xadjustment.register(self)
+        self.yadjustment.register(self)
+        
+    def render(self, width, height, st, at):
+
+        self.width = width
+        self.height = height
+        
+        child_width = self.child_width or width
+        child_height = self.child_height or height
+
+        surf = render(self.child, child_width, child_height, st, at)
+
+        cw, ch = surf.get_size()
+
+        # width = min(cw, width)
+        # height = min(ch, height)
+
+        if self.set_adjustments:
+            self.xadjustment.range = max(cw - width, 0)
+            self.xadjustment.page = width
+            self.yadjustment.range = max(ch - height, 0)
+            self.yadjustment.page = height
+
+        if self.xoffset is not None:
+            if isinstance(self.xoffset, int):
+                value = self.xoffset
+            else:
+                value = max(cw - width, 0) * self.xoffset
+                
+            self.xadjustment.value = value
+            
+        if self.yoffset is not None:
+            if isinstance(self.yoffset, int):
+                value = self.yoffset
+            else:
+                value = max(ch - height, 0) * self.yoffset 
+
+            self.yadjustment.value = value
+                
+        if self.edge_size and self.edge_last_st and (self.edge_xspeed or self.edge_yspeed):
+             
+            duration = max(st - self.edge_last_st, 0)
+            self.xadjustment.change(self.xadjustment.value + duration * self.edge_xspeed)
+            self.yadjustment.change(self.yadjustment.value + duration * self.edge_yspeed)
+
+            self.check_edge_redraw()
+            
+        self.edge_last_st = st
+             
+        cxo = -int(self.xadjustment.value)
+        cyo = -int(self.yadjustment.value)
+
+        self.offsets = [ (cxo, cyo) ]
+
+        rv = renpy.display.render.Render(width, height)
+        rv.blit(surf, (cxo, cyo))
+
+        return rv
+
+    def check_edge_redraw(self):
+        redraw = False
+        
+        if (self.edge_xspeed > 0) and (self.xadjustment.value < self.xadjustment.range):
+            redraw = True
+        if (self.edge_xspeed < 0) and (self.xadjustment.value > 0):
+            redraw = True
+
+        if (self.edge_yspeed > 0) and (self.yadjustment.value < self.yadjustment.range):
+            redraw = True
+        if (self.edge_yspeed < 0) and (self.yadjustment.value > 0):
+            redraw = True
+        
+        if redraw:
+            renpy.display.render.redraw(self, 0)
+            
+
+    def event(self, ev, x, y, st):
+
+        self.xoffset = None
+        self.yoffset = None
+
+        rv = super(Viewport, self).event(ev, x, y, st)
+        if rv is not None:
+            return rv
+
+        if self.draggable and renpy.display.focus.get_grab() == self:
+
+            oldx, oldy = self.drag_position
+            dx = x - oldx
+            dy = y - oldy
+
+            self.xadjustment.change(self.xadjustment.value - dx)
+            self.yadjustment.change(self.yadjustment.value - dy)
+
+            self.drag_position = (x, y) # W0201
+            
+            if renpy.display.behavior.map_event(ev, 'viewport_drag_end'):
+                renpy.display.focus.set_grab(None)
+                raise renpy.display.core.IgnoreEvent()
+                
+        if not ((0 <= x < self.width) and (0 <= y <= self.height)):
+            return
+                
+        if self.mousewheel:
+
+            if renpy.display.behavior.map_event(ev, 'viewport_up'):
+                rv = self.yadjustment.change(self.yadjustment.value - self.yadjustment.step)
+                if rv is not None:
+                    return rv
+                else:
+                    raise renpy.display.core.IgnoreEvent()
+
+            if renpy.display.behavior.map_event(ev, 'viewport_down'):
+                rv = self.yadjustment.change(self.yadjustment.value + self.yadjustment.step)
+                if rv is not None:
+                    return rv
+                else:
+                    raise renpy.display.core.IgnoreEvent()
+
+        if self.draggable:
+
+            if renpy.display.behavior.map_event(ev, 'viewport_drag_start'):
+                self.drag_position = (x, y)
+                renpy.display.focus.set_grab(self)
+                raise renpy.display.core.IgnoreEvent()
+                
+        if self.edge_size:
+            
+            def speed(n, zero, one):
+                """
+                Given a position `n`, computes the speed. The speed is 0.0
+                when `n` == `zero`, 1.0 when `n` == `one`, and linearly 
+                interpolated when between.                
+                
+                Returns 0.0 when outside the bounds - in either direction.
+                """
+                
+                n = 1.0 * (n - zero) / (one - zero)
+                if n < 0.0:
+                    return 0.0
+                if n > 1.0:
+                    return 0.0
+                
+                return n
+
+            xspeed = speed(x, self.width - self.edge_size, self.width)            
+            xspeed -= speed(x, self.edge_size, 0)
+            self.edge_xspeed = self.edge_speed * self.edge_function(xspeed)
+            
+            yspeed = speed(y, self.height - self.edge_size, self.height)
+            yspeed -= speed(y, self.edge_size, 0)
+            self.edge_yspeed = self.edge_speed * self.edge_function(yspeed)
+            
+            if xspeed or yspeed:
+                self.check_edge_redraw()
+                if self.edge_last_st is None:
+                    self.edge_last_st = st
+            else:
+                self.edge_last_st = None
+                
+        return None
+    
+    def set_xoffset(self, offset):
+        self.xoffset = offset
+        renpy.display.render.redraw(self, 0)
+        
+    def set_yoffset(self, offset):
+        self.yoffset = offset
+        renpy.display.render.redraw(self, 0)
+        
+def LiveCrop(rect, child, **properties):
+    """
+    :doc: disp_imagelike
+
+    This created a displayable by cropping `child` to `rect`, where
+    `rect` is an (x, y, width, height) tuple. ::
+
+        image eileen cropped = LiveCrop((0, 0, 300, 300), "eileen happy")    
+    """
+    
+    x, y, w, h = rect
+
+    return Viewport(child, offsets=(x, y), xmaximum=w, ymaximum=h, **properties)
+
+class Side(Container):
+
+    possible_positions = set([ 'tl', 't', 'tr', 'r', 'br', 'b', 'bl', 'l', 'c'])
+
+    def after_setstate(self):
+        self.sized = False
+    
+    def __init__(self, positions, style='side', **properties):
+
+        super(Side, self).__init__(style=style, **properties)
+
+        if isinstance(positions, str):
+            positions = positions.split()
+        
+        for i in positions:
+            if not i in Side.possible_positions:
+                raise Exception("Side used with impossible position '%s'." % (i,))
+
+        self.positions = tuple(positions)
+        self.sized = False
+        
+    def render(self, width, height, st, at):
+
+        pos_d = { }
+        pos_i = { }
+        
+        for i, (pos, d) in enumerate(zip(self.positions, self.children)):
+            pos_d[pos] = d
+            pos_i[pos] = i
+
+        # Figure out the size of each widget (and hence where the
+        # widget needs to be placed).
+
+        if not self.sized:
+            self.sized = True
+            
+            # Deal with various spacings.
+            spacing = self.style.spacing
+            
+            def spacer(a, b, c, axis):
+                if (a in pos_d) or (b in pos_d) or (c in pos_d):
+                    return spacing, axis - spacing
+                else:
+                    return 0, axis
+                
+            self.left_space, width = spacer('tl', 'l', 'bl', width) # W0201
+            self.right_space, width = spacer('tr', 'r', 'br', width) # W0201
+            self.top_space, height = spacer('tl', 't', 'tr', height) # W0201
+            self.bottom_space, height = spacer('bl', 'b', 'br', height) # W0201
+            
+            # The sizes of the various borders.
+            left = 0
+            right = 0
+            top = 0
+            bottom = 0
+            cwidth = 0
+            cheight = 0
+            
+            def sizeit(pos, width, height, owidth, oheight):
+                if pos not in pos_d:
+                    return owidth, oheight
+                
+                rend = render(pos_d[pos], width, height, st, at)
+                rv = max(owidth, rend.width), max(oheight, rend.height)
+                rend.kill()
+                return rv
+                
+            cwidth, cheight = sizeit('c', width, height, 0, 0)
+            cwidth, top = sizeit('t', cwidth, height, cwidth, top)
+            cwidth, bottom = sizeit('b', cwidth, height, cwidth, bottom)
+            left, cheight = sizeit('l', width, cheight, left, cheight) 
+            right, cheight = sizeit('r', width, cheight, right, cheight) 
+
+            left, top = sizeit('tl', left, top, left, top)
+            left, bottom = sizeit('bl', left, bottom, left, bottom)
+            right, top = sizeit('tr', right, top, right, top)
+            right, bottom = sizeit('br', right, bottom, right, bottom)
+            
+            self.cwidth = cwidth # W0201
+            self.cheight = cheight # W0201
+
+            self.top = top # W0201
+            self.bottom = bottom # W0201
+            self.left = left # W0201
+            self.right = right # W0201
+
+        else:
+            cwidth = self.cwidth
+            cheight = self.cheight
+            top = self.top
+            bottom = self.bottom
+            left = self.left
+            right = self.right
+        
+        # Now, place everything onto the render.
+        
+        self.offsets = [ None ] * len(self.children)
+
+        lefts = self.left_space
+        rights = self.right_space
+        tops = self.top_space
+        bottoms = self.bottom_space
+
+
+        cwidth = min(cwidth, width - left - lefts - right - rights)
+        cheight = min(cheight, height - top - tops - bottom - bottoms)
+        
+        rv = renpy.display.render.Render(left + lefts + cwidth + rights + right,
+                                         top + tops + cheight + bottoms + bottom)
+
+        def place(pos, x, y, w, h):
+
+            if pos not in pos_d:
+                return
+
+            d = pos_d[pos]
+            i = pos_i[pos]
+            rend = render(d, w, h, st, at)
+            self.offsets[i] = pos_d[pos].place(rv, x, y, w, h, rend)
+            
+        col1 = 0
+        col2 = left + lefts
+        col3 = left + lefts + cwidth + rights
+
+        row1 = 0
+        row2 = top + tops
+        row3 = top + tops + cheight + bottoms
+
+        place('c', col2, row2, cwidth, cheight)
+
+        place('t', col2, row1, cwidth, top)
+        place('r', col3, row2, right, cheight)
+        place('b', col2, row3, cwidth, bottom)
+        place('l', col1, row2, left, cheight)
+
+        place('tl', col1, row1, left, top)
+        place('tr', col3, row1, right, top)
+        place('br', col3, row3, right, bottom)
+        place('bl', col1, row3, left, bottom)
+
+        return rv
+        
+class Alpha(renpy.display.core.Displayable):
+    def __init__(self, start, end, time, child=None, repeat=False, bounce=False,
+                 anim_timebase=False, time_warp=None, **properties):
+
+        super(Alpha, self).__init__(**properties)
+
+        self.start = start
+        self.end = end
+        self.time = time
+        self.child = renpy.easy.displayable(child)
+        self.repeat = repeat
+        self.anim_timebase = anim_timebase
+        self.time_warp = time_warp
+        
+    def visit(self):
+        return [ self.child ]
+
+    def render(self, height, width, st, at):
+        if self.anim_timebase:
+            t = at
+        else:
+            t = st
+        
+        if self.time:
+            done = min(t / self.time, 1.0)
+        else:
+            done = 1.0
+
+        if renpy.game.less_updates:
+            done = 1.0
+        elif self.repeat:
+            done = done % 1.0
+            renpy.display.render.redraw(self, 0)
+        elif done != 1.0:
+            renpy.display.render.redraw(self, 0)
+            
+        if self.time_warp:
+            done = self.time_warp(done)
+
+        alpha = self.start + done * (self.end - self.start)
+            
+        rend = renpy.display.render.render(self.child, height, width, st, at)
+
+        w, h = rend.get_size()
+        rv = renpy.display.render.Render(w, h)
+        rv.blit(rend, (0, 0))
+        rv.alpha = alpha
+
+        return rv
+        
+        
+class AdjustTimes(Container):
+
+    def __init__(self, child, start_time, anim_time, **properties):
+        super(AdjustTimes, self).__init__(**properties)
+
+        self.start_time = start_time
+        self.anim_time = anim_time
+
+        self.add(child)
+    
+    def render(self, w, h, st, at):
+
+        if self.start_time is None:
+            self.start_time = renpy.game.interface.frame_time
+
+        if self.anim_time is None:
+            self.anim_time = renpy.game.interface.frame_time
+
+        st = renpy.game.interface.frame_time - self.start_time
+        at = renpy.game.interface.frame_time - self.anim_time       
+            
+        cr = renpy.display.render.render(self.child, w, h, st, at)
+        cw, ch = cr.get_size()
+        rv = renpy.display.render.Render(cw, ch)
+        rv.blit(cr, (0, 0))
+
+        self.offsets = [ (0, 0) ]
+        
+        return rv
+
+    def get_placement(self):
+        return self.child.get_placement()
+
+
+class LiveTile(Container):
+    """
+    :doc: disp_imagelike
+    
+    Tiles `child` until it fills the area allocated to this displayable.
+
+    ::
+
+        image bg tile = LiveTile("bg.png")
+
+    """
+    
+    def __init__(self, child, style='tile', **properties):
+        super(LiveTile, self).__init__(style=style, **properties)
+
+        self.add(child)
+
+    def render(self, width, height, st, at):
+
+        cr = renpy.display.render.render(self.child, width, height, st, at)
+        cw, ch = cr.get_size()
+        rv = renpy.display.render.Render(width, height)
+
+        width = int(width)
+        height = int(height)
+        cw = int(cw)
+        ch = int(ch)
+
+        for y in range(0, height, ch):
+            for x in range(0, width, cw):
+                rv.blit(cr, (x, y), focus=False)
+
+        return rv
diff --git a/unrpyc/renpy/display/minigame.py b/unrpyc/renpy/display/minigame.py
new file mode 100644
index 0000000..7469922
--- /dev/null
+++ b/unrpyc/renpy/display/minigame.py
@@ -0,0 +1,25 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+
+def Minigame(*args, **kwargs):
+    raise Exception("Minigame is no longer implemented.")
+
diff --git a/unrpyc/renpy/display/module.py b/unrpyc/renpy/display/module.py
new file mode 100644
index 0000000..1bf66e0
--- /dev/null
+++ b/unrpyc/renpy/display/module.py
@@ -0,0 +1,275 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file mediates access to the _renpy module, which is a C module that
+# allows us to enhance the feature set of pygame in a renpy specific way.
+
+VERSION = (6, 12, 0)
+
+import renpy.display
+import pygame; pygame # prevents pyflakes warning.
+
+import sys
+
+try:
+    import _renpy
+    version = _renpy.version()
+
+    if version != VERSION:
+        print("Found Ren'Py module version %s, while expecting %s." % (
+            ".".join(str(i) for i in version),
+            ".".join(str(i) for i in VERSION),
+            ))
+
+        print("Trying to run anyway, but you should expect errors.", file=sys.stderr)
+        
+except:
+    print("The _renpy module was not found. Please read module/README.txt for", file=sys.stderr)
+    print("more information.", file=sys.stderr)
+
+    sys.exit(-1)
+
+def convert_and_call(function, src, dst, *args):
+    """
+    This calls the function with the source and destination
+    surface. The surfaces must have the same alpha.
+
+    If the surfaces are not 24 or 32 bits per pixel, or don't have the
+    same format, they are converted and then converted back.
+    """
+
+    # Now that all surfaces are 32bpp, this function doesn't do much
+    # of anything anymore.
+    
+    if (dst.get_masks()[3] != 0) != (src.get_masks()[3] != 0):
+        raise Exception("Surface alphas do not match.")
+
+    function(src, dst, *args)
+
+
+def pixellate(src, dst, avgwidth, avgheight, outwidth, outheight):
+    """
+    This pixellates the source surface. First, every pixel in the
+    source surface is projected onto a virtual surface, such that
+    the average value of every avgwidth x avgheight pixels becomes
+    one virtual pixel. It then gets projected back onto the
+    destination surface at a ratio of one virtual pixel to every
+    outwidth x outheight destination pixels.
+
+    If either src or dst is not a 24 or 32 bit surface, they are
+    converted... but that may be a significant performance hit.
+
+    The two surfaces must either have the same alpha or no alpha.
+    """
+
+    convert_and_call(_renpy.pixellate,
+                     src, dst,
+                     avgwidth, avgheight,
+                     outwidth, outheight)
+
+
+def scale(s, size):
+    """
+    Scales down the supplied pygame surface by the given X and Y
+    factors.
+
+    Always works, but may not be high quality.
+    """
+
+    d = renpy.display.pgrender.surface(size, True)
+
+    bilinear_scale(s, d)
+    
+    return d
+
+
+# What we have here are a pair of tables mapping masks to byte offsets
+# for 24 and 32 bpp modes. We represent 0xff000000 as positive and negative
+# numbers so that it doesn't yield a warning, and so that it works on
+# 32 and 64 bit platforms.
+if sys.byteorder == 'big':
+    bo32 = { 255 : 3, 65280 : 2, 16711680 : 1, 4278190080 : 0, -16777216 : 0, }
+else:
+    bo32 = { 255 : 0, 65280 : 1, 16711680 : 2, 4278190080 : 3, -16777216 : 3, }
+
+bo_cache = None
+
+def byte_offset(src):
+    """
+    Given the surface src, returns a 4-tuple giving the byte offsets
+    for the red, green, blue, and alpha components of the pixels in
+    the surface. If a component doesn't exist, None is returned.
+    """
+
+    global bo_cache
+    
+    if bo_cache is None:
+        bo_cache = [ bo32[i] for i in src.get_masks() ]
+        
+    return bo_cache
+
+def endian_order(src, r, g, b, a):
+
+    if bo_cache is None:
+        byte_offset(src)
+
+    rv = [ a, a, a, a ]
+
+    for i, index_i in zip((r, g, b, a), bo_cache):
+        rv[index_i] = i
+
+    return rv
+
+
+
+def linmap(src, dst, rmap, gmap, bmap, amap):
+    """
+    This maps the colors between two surfaces. The various map
+    parameters should be fixed-point integers, with 1.0 == 256.
+    """
+
+    convert_and_call(_renpy.linmap,
+                     src, dst,
+                     *endian_order(dst, rmap, gmap, bmap, amap))
+
+
+save_png = _renpy.save_png
+
+def map(src, dst, rmap, gmap, bmap, amap): #@ReservedAssignment
+    """
+    This maps the colors between two surfaces. The various map
+    parameters must be 256 character long strings, with the value
+    of a character at a given offset being what a particular pixel
+    component value is mapped to.
+    """
+
+    convert_and_call(_renpy.map,
+                     src, dst,
+                     *endian_order(dst, rmap, gmap, bmap, amap))
+
+
+
+def twomap(src, dst, white, black):
+    """
+    Given colors for white and black, linearly maps things
+    appropriately, taking the alpha channel from white.
+    """
+
+    wr = white[0]
+    wg = white[1]
+    wb = white[2]
+    wa = white[3]
+
+    br = black[0]
+    bg = black[1]
+    bb = black[2]
+
+    ramp = renpy.display.im.ramp
+
+    if br == 0 and bg == 0 and bb == 0:
+        linmap(src, dst,
+               wr + 1,
+               wg + 1,
+               wb + 1,
+               wa + 1)
+    else:
+        list(map(src, dst,
+            ramp(br, wr),
+            ramp(bg, wg),
+            ramp(bb, wb),
+            ramp(0, wa)))
+
+
+def alpha_munge(src, dst, amap):
+    """
+    This samples the red channel from src, maps it through amap, and
+    place it into the alpha channel of amap.
+    """
+
+    if src.get_size() != dst.get_size():
+        return
+
+    red = byte_offset(src)[0]
+    alpha = byte_offset(dst)[3]
+
+    if red is not None and alpha is not None:
+        _renpy.alpha_munge(src, dst, red, alpha, amap)        
+
+
+def bilinear_scale(src, dst, sx=0, sy=0, sw=None, sh=None, dx=0, dy=0, dw=None, dh=None, precise=0):
+
+    if sw is None:
+        sw, sh = src.get_size()
+    if dw is None:
+        dw, dh = dst.get_size()
+
+    while True:
+
+        if sw <= dw * 2 and sh <= dh * 2:
+            break
+
+        nsw = max(sw / 2, dw)
+        nsh = max(sh / 2, dh)
+
+        nsrc = renpy.display.pgrender.surface((nsw, nsh), src.get_masks()[3])
+
+        _renpy.bilinear(src, nsrc, sx, sy, sw, sh, precise=precise)
+
+        sx = 0
+        sy = 0
+        sw = nsw
+        sh = nsh
+        src = nsrc
+
+    _renpy.bilinear(src, dst, sx, sy, sw, sh, dx, dy, dw, dh, precise=precise)
+        
+
+transform = _renpy.transform
+    
+# Note: Blend requires all surfaces to be the same size.    
+blend = _renpy.blend
+    
+def imageblend(a, b, dst, img, amap):        
+    alpha = byte_offset(img)[3]
+    _renpy.imageblend(a, b, dst, img, alpha, amap)
+
+
+def colormatrix(src, dst, matrix):
+    c = [ matrix[0:5], matrix[5:10], matrix[10:15], matrix[15:20] ]
+    offs = byte_offset(src)
+
+    o = [ None ] * 4
+    for i in range(0, 4):
+        o[offs[i]] = i
+
+    _renpy.colormatrix(src, dst,
+                       c[o[0]][o[0]], c[o[0]][o[1]], c[o[0]][o[2]], c[o[0]][o[3]], c[o[0]][4],    
+                       c[o[1]][o[0]], c[o[1]][o[1]], c[o[1]][o[2]], c[o[1]][o[3]], c[o[1]][4],    
+                       c[o[2]][o[0]], c[o[2]][o[1]], c[o[2]][o[2]], c[o[2]][o[3]], c[o[2]][4],    
+                       c[o[3]][o[0]], c[o[3]][o[1]], c[o[3]][o[2]], c[o[3]][o[3]], c[o[3]][4])
+
+
+def subpixel(src, dst, x, y):
+
+    shift = src.get_shifts()[3]
+    _renpy.subpixel(src, dst, x, y, shift)
+
+    
diff --git a/unrpyc/renpy/display/motion.py b/unrpyc/renpy/display/motion.py
new file mode 100644
index 0000000..1c9d667
--- /dev/null
+++ b/unrpyc/renpy/display/motion.py
@@ -0,0 +1,1526 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains displayables that move, zoom, rotate, or otherwise
+# transform displayables. (As well as displayables that support them.)
+import math
+import types #@UnresolvedImport
+
+import renpy.display #@UnusedImport
+from renpy.display.render import render
+from renpy.display.layout import Container
+
+import renpy.display.accelerator
+
+# The null object that's used if we don't have a defined child.
+null = None 
+
+def get_null():
+    global null
+    
+    if null is None:
+        null = renpy.display.layout.Null()
+        
+    return null
+
+# Convert a position from cartesian to polar coordinates.
+def cartesian_to_polar(x, y, xaround, yaround):
+    """
+    Converts cartesian coordinates to polar coordinates.
+    """
+
+    dx = x - xaround
+    dy = y - yaround
+
+    radius = math.hypot(dx, dy)
+    angle = math.atan2(dx, -dy) / math.pi * 180
+
+    if angle < 0:
+        angle += 360
+    
+    return angle, radius
+    
+def polar_to_cartesian(angle, radius, xaround, yaround):
+    """
+    Converts polart coordinates to cartesian coordinates.
+    """
+    
+    angle = angle * math.pi / 180
+    
+    dx = radius * math.sin(angle)
+    dy = -radius * math.cos(angle)
+    
+    x = type(xaround)(xaround + dx)
+    y = type(yaround)(yaround + dy)
+    
+    return x, y
+
+def first_not_none(*args):
+    """
+    Returns the first argument that is not None.
+    """
+
+    for i in args:
+        if i is not None:
+            return i
+    return i
+
+
+class TransformState(renpy.object.Object):
+
+    xoffset = None
+    yoffset = None
+    default_xpos = None
+    default_ypos = None
+    default_xanchor = None
+    default_yanchor = None
+    default_xoffset = None
+    default_yoffset = None
+    transform_anchor = False
+    
+    def __init__(self): # W0231
+        self.alpha = 1
+        self.rotate = None
+        self.rotate_pad = True
+        self.transform_anchor = False
+        self.zoom = 1
+        self.xzoom = 1
+        self.yzoom = 1
+        
+        self.xpos = None
+        self.ypos = None
+        self.xanchor = None
+        self.yanchor = None
+        self.xoffset = 0
+        self.yoffset = 0
+        
+        self.xaround = 0.0
+        self.yaround = 0.0
+        self.xanchoraround = 0.0
+        self.yanchoraround = 0.0
+
+        self.subpixel = False
+
+        self.crop = None
+        self.corner1 = None
+        self.corner2 = None
+        self.size = None
+
+        self.delay = 0
+
+        # Note: When adding a new property, we need to add it to:
+        # - take_state
+        # - diff
+        # - renpy.atl.PROPERTIES
+        # - Proxies in Transform
+        
+        # Default values for various properties, taken from our
+        # parent.
+        self.default_xpos = None
+        self.default_ypos = None
+        self.default_xanchor = None
+        self.default_yanchor = None
+        
+    def take_state(self, ts):
+
+        self.alpha = ts.alpha
+        self.rotate = ts.rotate
+        self.rotate_pad = ts.rotate_pad
+        self.transform_anchor = ts.transform_anchor
+        self.zoom = ts.zoom
+        self.xzoom = ts.xzoom
+        self.yzoom = ts.yzoom
+        
+        self.xaround = ts.xaround
+        self.yaround = ts.yaround
+        self.xanchoraround = ts.xanchoraround
+        self.yanchoraround = ts.yanchoraround
+
+        self.subpixel = ts.subpixel
+
+        self.crop = ts.crop
+        self.corner1 = ts.corner1
+        self.corner2 = ts.corner2
+        self.size = ts.size
+
+        # Take the computed position properties, not the
+        # raw ones.
+        (self.default_xpos,
+         self.default_ypos,         
+         self.default_xanchor,
+         self.default_yanchor,
+         self.xoffset,
+         self.yoffset,
+         self.subpixel) = ts.get_placement()
+        
+    # Returns a dict, with p -> (old, new) where p is a property that
+    # has changed between this object and the new object.
+    def diff(self, newts):
+
+        rv = { }
+
+        def diff2(prop, new, old):
+            if new != old:
+                rv[prop] = (old, new)
+
+        def diff4(prop, new, default_new, old, default_old):
+            if new is None:
+                new_value = default_new
+            else:
+                new_value = new
+                
+            if old is None:
+                old_value = default_old
+            else:
+                old_value = old
+                
+            if new_value != old_value:
+                rv[prop] = (old_value, new_value)
+
+        diff2("alpha", newts.alpha, self.alpha)
+        diff2("rotate", newts.rotate, self.rotate)
+        diff2("rotate_pad", newts.rotate_pad, self.rotate_pad)
+        diff2("transform_anchor", newts.transform_anchor, self.transform_anchor)
+        diff2("zoom", newts.zoom, self.zoom)
+        diff2("xzoom", newts.xzoom, self.xzoom)
+        diff2("yzoom", newts.yzoom, self.yzoom)
+        
+        diff2("xaround", newts.xaround, self.xaround)
+        diff2("yaround", newts.yaround, self.yaround)
+        diff2("xanchoraround", newts.xanchoraround, self.xanchoraround)
+        diff2("yanchoraround", newts.yanchoraround, self.yanchoraround)
+
+        diff2("subpixel", newts.subpixel, self.subpixel)
+
+        diff2("crop", newts.crop, self.crop)
+        diff2("corner1", newts.corner1, self.corner1)
+        diff2("corner2", newts.corner2, self.corner2)
+        diff2("size", newts.size, self.size)
+
+        diff4("xpos", newts.xpos, newts.default_xpos, self.xpos, self.default_xpos)
+
+        diff4("xanchor", newts.xanchor, newts.default_xanchor, self.xanchor, self.default_xanchor)
+        diff2("xoffset", newts.xoffset, self.xoffset)
+
+        diff4("ypos", newts.ypos, newts.default_ypos, self.ypos, self.default_ypos)
+        diff4("yanchor", newts.yanchor, newts.default_yanchor, self.yanchor, self.default_yanchor)
+        diff2("yoffset", newts.yoffset, self.yoffset)
+        
+        return rv
+
+    def get_placement(self, cxoffset=0, cyoffset=0):
+
+        return (
+            first_not_none(self.xpos, self.default_xpos),
+            first_not_none(self.ypos, self.default_ypos),
+            first_not_none(self.xanchor, self.default_xanchor),
+            first_not_none(self.yanchor, self.default_yanchor),
+            self.xoffset + cxoffset,
+            self.yoffset + cyoffset,
+            self.subpixel,
+            )
+                    
+    # These update various properties.
+    def get_xalign(self):
+        return self.xpos 
+
+    def set_xalign(self, v):
+        self.xpos = v
+        self.xanchor = v
+
+    xalign = property(get_xalign, set_xalign)
+
+    def get_yalign(self):
+        return self.ypos 
+
+    def set_yalign(self, v):
+        self.ypos = v
+        self.yanchor = v
+
+    yalign = property(get_yalign, set_yalign)
+
+    def get_around(self):
+        return (self.xaround, self.yaround)
+    
+    def set_around(self, value):
+        self.xaround, self.yaround = value
+        self.xanchoraround, self.yanchoraround = None, None
+
+    def set_alignaround(self, value):
+        self.xaround, self.yaround = value
+        self.xanchoraround, self.yanchoraround = value
+        
+    around = property(get_around, set_around)
+    alignaround = property(get_around, set_alignaround)
+        
+    def get_angle(self):
+        xpos = first_not_none(self.xpos, self.default_xpos, 0)
+        ypos = first_not_none(self.ypos, self.default_ypos, 0)
+        angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
+        return angle
+
+    def get_radius(self):
+        xpos = first_not_none(self.xpos, self.default_xpos, 0)
+        ypos = first_not_none(self.ypos, self.default_ypos, 0)
+        _angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
+        return radius
+
+    def set_angle(self, value):
+        xpos = first_not_none(self.xpos, self.default_xpos, 0)
+        ypos = first_not_none(self.ypos, self.default_ypos, 0)
+        _angle, radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
+        angle = value
+        self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
+
+        if self.xanchoraround:
+            self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
+        
+    def set_radius(self, value):
+        xpos = first_not_none(self.xpos, self.default_xpos, 0)
+        ypos = first_not_none(self.ypos, self.default_ypos, 0)
+        angle, _radius = cartesian_to_polar(xpos, ypos, self.xaround, self.yaround)
+        radius = value
+        self.xpos, self.ypos = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
+
+        if self.xanchoraround:
+            self.xanchor, self.yanchor = polar_to_cartesian(angle, radius, self.xaround, self.yaround)
+        
+    angle = property(get_angle, set_angle)
+    radius = property(get_radius, set_radius)
+
+    def get_pos(self):
+        return self.xpos, self.ypos
+
+    def set_pos(self, value):
+        self.xpos, self.ypos = value
+
+    pos = property(get_pos, set_pos)
+
+    def get_anchor(self):
+        return self.xanchor, self.yanchor
+
+    def set_anchor(self, value):
+        self.xanchor, self.yanchor = value
+
+    anchor = property(get_anchor, set_anchor)
+
+    def get_align(self):
+        return self.xpos, self.ypos
+
+    def set_align(self, value):
+        self.xanchor, self.yanchor = value
+        self.xpos, self.ypos = value
+
+    align = property(get_align, set_align)
+
+    def get_offset(self):
+        return self.xoffset, self.yoffset
+
+    def set_offset(self, value):
+        self.xoffset, self.yoffset = value
+
+    offset = property(get_offset, set_offset)
+
+    def set_xcenter(self, value):
+        self.xpos = value
+        self.xanchor = 0.5
+
+    def get_xcenter(self):
+        return self.xpos
+
+    def set_ycenter(self, value):
+        self.ypos = value
+        self.yanchor = 0.5
+
+    def get_ycenter(self):
+        return self.ypos
+
+    xcenter = property(get_xcenter, set_xcenter)
+    ycenter = property(get_ycenter, set_ycenter)
+    
+class Proxy(object):
+    """
+    This class proxies a field from the transform to its state.
+    """
+
+    def __init__(self, name):
+        self.name = name
+
+    def __get__(self, instance, owner):
+        return getattr(instance.state, self.name)
+
+    def __set__(self, instance, value):
+        return setattr(instance.state, self.name, value)
+    
+class Transform(Container):
+    """
+    Documented in sphinx, because we can't scan this object.
+    """
+
+    __version__ = 5
+    transform_event_responder = True
+    
+    # Proxying things over to our state.
+    alpha = Proxy("alpha")
+    rotate = Proxy("rotate")
+    rotate_pad = Proxy("rotate_pad")
+    transform_anchor = Proxy("rotate_pad")
+    zoom = Proxy("zoom")
+    xzoom = Proxy("xzoom")
+    yzoom = Proxy("yzoom")
+
+    xpos = Proxy("xpos")
+    ypos = Proxy("ypos")
+    xanchor = Proxy("xanchor")
+    yanchor = Proxy("yanchor")
+    
+    xalign = Proxy("xalign")
+    yalign = Proxy("yalign")
+
+    around = Proxy("around")
+    alignaround = Proxy("alignaround")
+    angle = Proxy("angle")
+    radius = Proxy("radius")
+
+    xaround = Proxy("xaround")
+    yaround = Proxy("yaround")
+    xanchoraround = Proxy("xanchoraround")
+    yanchoraround = Proxy("yanchoraround")
+
+    pos = Proxy("pos")
+    anchor = Proxy("anchor")
+    align = Proxy("align")
+    
+    crop = Proxy("crop")
+    corner1 = Proxy("corner1")
+    corner2 = Proxy("corner2")
+    size = Proxy("size")
+
+    delay = Proxy("delay")
+
+    xoffset = Proxy("xoffset")
+    yoffset = Proxy("yoffset")
+    offset = Proxy("offset")
+
+    subpixel = Proxy("subpixel")
+
+    xcenter = Proxy("xcenter")
+    ycenter = Proxy("ycenter")
+    
+    def after_upgrade(self, version):
+
+        if version < 1:
+            self.active = False
+            self.state = TransformState()
+
+            self.state.xpos = self.xpos or 0
+            self.state.ypos = self.ypos or 0
+            self.state.xanchor = self.xanchor or 0
+            self.state.yanchor = self.yanchor or 0
+            self.state.alpha = self.alpha
+            self.state.rotate = self.rotate
+            self.state.zoom = self.zoom
+            self.state.xzoom = self.xzoom
+            self.state.yzoom = self.yzoom
+
+            self.hide_request = False
+            self.hide_response = True
+
+        if version < 2:
+            self.st = 0
+            self.at = 0
+
+        if version < 3:
+            self.st_offset = 0
+            self.at_offset = 0
+            self.child_st_base = 0
+
+        if version < 4:
+            self.style_arg = 'transform'
+
+        if version < 5:
+            self.replaced_request = False
+            self.replaced_response = True
+            
+    DEFAULT_ARGUMENTS = {
+            "selected_activate" : { },
+            "selected_hover" : { },
+            "selected_idle" : { },
+            "selected_insensitive" : { },
+            "activate" : { },
+            "hover" : { },
+            "idle" : { },
+            "insensitive" : { },
+            "" : { },
+            }
+
+    # Compatibility with old versions of the class.
+    active = False
+    children = False
+    arguments = DEFAULT_ARGUMENTS
+    
+    def __init__(self,
+                 child=None,
+                 function=None,
+
+                 style='transform',
+                 focus=None,
+                 default=False,
+                
+                 **kwargs):
+
+        self.kwargs = kwargs
+        self.style_arg = style
+        
+        super(Transform, self).__init__(style=style, focus=focus, default=default)
+
+        self.function = function
+
+        child = renpy.easy.displayable_or_none(child)
+        if child is not None:
+            self.add(child)
+
+        self.state = TransformState()
+
+        self.arguments = dict((k, {}) for k in self.DEFAULT_ARGUMENTS)
+        
+        # Split up the keyword arguments.
+        for k, v in kwargs.items():
+            if "_" in k:
+                prefix, prop = k.rsplit("_", 1)
+            else:
+                prefix = ""
+                prop = k
+
+            if prefix not in self.arguments:
+                raise Exception("Unknown transform property prefix: %r" % prefix)
+
+            if prop not in renpy.atl.PROPERTIES:
+                raise Exception("Unknown transform property: %r")
+            
+            self.arguments[prefix][prop] = v
+
+            
+        # Apply the keyword arguments.
+        for k, v in kwargs.items():
+            setattr(self.state, k, v)
+
+        # This is the matrix transforming our coordinates into child coordinates.
+        self.forward = None
+
+        # Have we called the function at least once?
+        self.active = False
+
+        # Have we been requested to hide?
+        self.hide_request = False
+        
+        # True if it's okay for us to hide.
+        self.hide_response = True
+
+        # Have we been requested to replaced?
+        self.replaced_request = False
+
+        # True if it's okay for us to replaced.
+        self.replaced_response = True
+        
+        self.st = 0
+        self.at = 0
+        self.st_offset = 0
+        self.at_offset = 0
+
+        self.child_st_base = 0
+
+    def visit(self):
+        if self.child is None:
+            return [ ]
+        else:        
+            return [ self.child ]
+    
+    # The default function chooses entries from self.arguments that match
+    # the style prefix, and applies them to the state.
+    def default_function(self, state, st, at):
+
+        prefix = self.style.prefix.strip("_")
+        prefixes = [ ]
+
+        while prefix:
+            prefixes.insert(0, prefix)
+            _, _, prefix = prefix.partition("_")
+
+        prefixes.insert(0, "")
+
+        for i in prefixes:
+            for k, v in self.arguments[i].items():
+                setattr(state, k, v)
+            
+        return None
+        
+    def set_transform_event(self, event):
+        if self.child is not None:
+            self.child.set_transform_event(event)
+        
+        super(Transform, self).set_transform_event(event)
+        
+        
+    def take_state(self, t):
+        """
+        Takes the transformation state from object t into this object.
+        """
+        
+        self.state.take_state(t.state)
+
+        # The arguments will be applied when the default function is
+        # called.
+
+            
+    def take_execution_state(self, t):
+        """
+        Takes the execution state from object t into this object. This is
+        overridden by renpy.atl.TransformBase.
+        """
+
+        self.hide_request = t.hide_request
+        self.replaced_request = t.replaced_request
+
+        self.state.xpos = t.state.xpos
+        self.state.ypos = t.state.ypos
+        self.state.xanchor = t.state.xanchor
+        self.state.yanchor = t.state.yanchor
+
+        if isinstance(self.child, Transform) and isinstance(t.child, Transform):
+            self.child.take_execution_state(t.child)
+
+
+    def copy(self):
+        """
+        Makes a copy of this transform.
+        """
+
+        d = self()
+        d.kwargs = { }
+        d.take_state(self)
+        d.take_execution_state(self)
+        d.st = self.st
+        d.at = self.at
+
+        return d
+
+    def _change_transform_child(self, child):
+        rv = self.copy()
+        
+        if self.child is not None:
+            rv.set_child(self.child._change_transform_child(child))
+        
+        return rv
+    
+    def _hide(self, st, at, kind):
+
+        if not self.child:
+            return None
+
+        if not (self.hide_request or self.replaced_request):
+            d = self.copy()
+        else:
+            d = self
+
+        d.st_offset = self.st_offset
+        d.at_offset = self.at_offset
+
+        if kind == "hide":
+            d.hide_request = True
+        else:
+            d.replaced_request = True
+
+        d.hide_response = True
+        d.replaced_response = True
+
+        if d.function is not None:
+            d.function(d, st + d.st_offset, at + d.at_offset)
+       
+        new_child = d.child._hide(st, at, kind)
+
+        if new_child is not None:
+            d.child = new_child
+            d.hide_response = False
+            d.replaced_response = False
+                
+        if (not d.hide_response) or (not d.replaced_response):
+            renpy.display.render.redraw(d, 0)
+            return d
+        
+        return None
+        
+    def set_child(self, child):
+        
+        child = renpy.easy.displayable(child)
+        
+        self.child = child
+        self.child_st_base = self.st
+
+        child.per_interact()
+
+        renpy.display.render.redraw(self, 0)
+        
+    def update_state(self):
+        """
+        This updates the state to that at self.st, self.at.
+        """
+
+        # If we have to, call the function that updates this transform.        
+        if self.function is not None:
+            fr = self.function(self, self.st, self.at)
+        else:
+            fr = self.default_function(self, self.st, self.at)
+
+        # Order a redraw, if necessary.
+        if fr is not None:
+            renpy.display.render.redraw(self, fr)
+
+        state = self.state
+        
+        self.active = True
+
+        # Use non-None elements of the child placement as defaults.
+        child = self.child
+        if child is not None and renpy.config.transform_uses_child_position:
+
+            pos = child.get_placement()
+
+            if pos[0] is not None:
+                state.default_xpos = pos[0]
+            if pos[2] is not None:
+                state.default_xanchor = pos[2]
+            if pos[1] is not None:
+                state.default_ypos = pos[1]
+            if pos[3] is not None:
+                state.default_yanchor = pos[3]
+
+            state.subpixel |= pos[6]   
+        
+    # The render method is now defined in accelerator.pyx.
+            
+    def event(self, ev, x, y, st):
+
+        if self.hide_request:
+            return None
+        
+        children = self.children
+        offsets = self.offsets
+        
+        if not offsets:
+            return None
+        
+        for i in range(len(self.children)-1, -1, -1):
+
+            d = children[i]
+            xo, yo = offsets[i]
+
+            cx = x - xo
+            cy = y - yo
+
+            # Transform screen coordinates to child coordinates.
+            cx, cy = self.forward.transform(cx, cy)
+
+            rv = d.event(ev, cx, cy, st)    
+            if rv is not None:
+                return rv
+                
+        return None
+    
+    def __call__(self, child=None, take_state=True):
+
+        if child is None:
+            child = self.child
+
+        # If we don't have a child for some reason, set it to null.            
+        if child is None:
+            child = get_null()
+        
+        rv = Transform(
+            child=child,
+            function=self.function,
+            style=self.style_arg,
+            **self.kwargs)
+        
+        rv.take_state(self)
+
+        return rv
+    
+    def get_placement(self):
+
+        if not self.active:
+            self.update_state()
+            
+        if self.child is not None:
+            _cxpos, _cypos, _cxanchor, _cyanchor, cxoffset, cyoffset, _csubpixel = self.child.get_placement()
+        else:
+            cxoffset = 0
+            cyoffset = 0
+
+        cxoffset = cxoffset or 0
+        cyoffset = cyoffset or 0 
+        
+        rv = self.state.get_placement(cxoffset, cyoffset)
+
+        if self.state.transform_anchor:
+
+            xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = rv
+            if (xanchor is not None) and (yanchor is not None):
+    
+                cw, ch = self.child_size
+                rw, rh = self.render_size
+                
+                if isinstance(xanchor, float):
+                    xanchor *= cw
+                if isinstance(yanchor, float):
+                    yanchor *= ch
+                
+                xanchor -= cw / 2.0
+                yanchor -= ch / 2.0
+            
+                xanchor, yanchor = self.reverse.transform(xanchor, yanchor)
+    
+                xanchor += rw / 2.0
+                yanchor += rh / 2.0
+    
+                xanchor = renpy.display.core.absolute(xanchor)
+                yanchor = renpy.display.core.absolute(yanchor)
+            
+                rv = (xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel)
+        
+        return rv
+
+    def update(self):
+        """
+        This should be called when a transform property field is updated outside
+        of the callback method, to ensure that the change takes effect.
+        """
+        
+        renpy.display.render.invalidate(self)
+
+    def parameterize(self, name, parameters):
+        if parameters:
+            raise Exception("Image '%s' can't take parameters '%s'. (Perhaps you got the name wrong?)" %
+                            (' '.join(name), ' '.join(parameters)))
+
+        # Note the call here.
+        return self()
+    
+    def _show(self):
+        self.update_state()
+        
+Transform.render = types.MethodType(renpy.display.accelerator.transform_render, None, Transform)
+
+class ATLTransform(renpy.atl.ATLTransformBase, Transform):
+    
+    def __init__(self, atl, child=None, context={}, parameters=None, **properties):
+        renpy.atl.ATLTransformBase.__init__(self, atl, context, parameters)
+        Transform.__init__(self, child=child, function=self.execute, **properties)
+
+        self.raw_child = self.child
+        
+    def _show(self):
+        super(ATLTransform, self)._show()
+        self.execute(self, self.st, self.at)
+    
+    
+class Motion(Container):
+    """
+    This is used to move a child displayable around the screen. It
+    works by supplying a time value to a user-supplied function,
+    which is in turn expected to return a pair giving the x and y
+    location of the upper-left-hand corner of the child, or a
+    4-tuple giving that and the xanchor and yanchor of the child.
+
+    The time value is a floating point number that ranges from 0 to
+    1. If repeat is True, then the motion repeats every period
+    sections. (Otherwise, it stops.) If bounce is true, the
+    time value varies from 0 to 1 to 0 again.
+
+    The function supplied needs to be pickleable, which means it needs
+    to be defined as a name in an init block. It cannot be a lambda or
+    anonymous inner function. If you can get away with using Pan or
+    Move, use them instead.
+
+    Please note that floats and ints are interpreted as for xpos and
+    ypos, with floats being considered fractions of the screen.
+    """
+
+    def __init__(self, function, period, child=None, new_widget=None, old_widget=None, repeat=False, bounce=False, delay=None, anim_timebase=False, tag_start=None, time_warp=None, add_sizes=False, style='motion', **properties):
+        """
+        @param child: The child displayable.
+
+        @param new_widget: If child is None, it is set to new_widget,
+        so that we can speak the transition protocol.
+
+        @param old_widget: Ignored, for compatibility with the transition protocol.
+
+        @param function: A function that takes a floating point value and returns
+        an xpos, ypos tuple.
+
+        @param period: The amount of time it takes to go through one cycle, in seconds.
+
+        @param repeat: Should we repeat after a period is up?
+
+        @param bounce: Should we bounce?
+
+        @param delay: How long this motion should take. If repeat is None, defaults to period.
+
+        @param anim_timebase: If True, use the animation timebase rather than the shown timebase.
+
+        @param time_warp: If not None, this is a function that takes a
+        fraction of the period (between 0.0 and 1.0), and returns a
+        new fraction of the period. Use this to warp time, applying
+        acceleration and deceleration to motions.
+
+        This can also be used as a transition. When used as a
+        transition, the motion is applied to the new_widget for delay
+        seconds.
+        """
+
+        if child is None:
+            child = new_widget
+            
+        if delay is None and not repeat:
+            delay = period
+
+        super(Motion, self).__init__(style=style, **properties)
+
+        if child is not None:
+            self.add(child)
+
+        self.function = function
+        self.period = period
+        self.repeat = repeat
+        self.bounce = bounce
+        self.delay = delay
+        self.anim_timebase = anim_timebase
+        self.time_warp = time_warp
+        self.add_sizes = add_sizes
+
+        self.position = None
+        
+
+    def get_placement(self):
+
+        if self.position is None:
+            return super(Motion, self).get_placement()
+        else:
+            return self.position + (self.style.xoffset, self.style.yoffset, self.style.subpixel)
+                
+    def render(self, width, height, st, at):
+
+        if self.anim_timebase:
+            t = at
+        else:
+            t = st
+
+        if renpy.game.less_updates:
+            if self.delay:
+                t = self.delay
+                if self.repeat:
+                    t = t % self.period
+            else:
+                t = self.period                    
+        elif self.delay and t >= self.delay:
+            t = self.delay            
+            if self.repeat:
+                t = t % self.period
+        elif self.repeat:
+            t = t % self.period
+            renpy.display.render.redraw(self, 0)
+        else:
+            if t > self.period:
+                t = self.period
+            else:
+                renpy.display.render.redraw(self, 0)
+
+        if self.period > 0:
+            t /= self.period
+        else:
+            t = 1
+
+        if self.time_warp:
+            t = self.time_warp(t)
+
+        if self.bounce:
+            t = t * 2
+            if t > 1.0:
+                t = 2.0 - t
+
+        child = render(self.child, width, height, st, at)
+        cw, ch = child.get_size()
+
+        if self.add_sizes:
+            res = self.function(t, (width, height, cw, ch))
+        else:
+            res = self.function(t)
+
+        res = tuple(res)
+            
+        if len(res) == 2:
+            self.position = res + (self.style.xanchor, self.style.yanchor)
+        else:
+            self.position = res
+
+        rv = renpy.display.render.Render(cw, ch)
+        rv.blit(child, (0, 0))
+
+        self.offsets = [ (0, 0) ]
+
+        return rv
+
+        
+class Interpolate(object):
+
+    anchors = {
+        'top' : 0.0,
+        'center' : 0.5,
+        'bottom' : 1.0,
+        'left' : 0.0,
+        'right' : 1.0,
+        }
+
+    def __init__(self, start, end):
+
+        if len(start) != len(end):
+            raise Exception("The start and end must have the same number of arguments.")
+
+        self.start = [ self.anchors.get(i, i) for i in start ]
+        self.end = [ self.anchors.get(i, i) for i in end ]
+
+    def __call__(self, t, sizes=(None, None, None, None)):
+
+        def interp(a, b, c):
+
+            if c is not None:
+                if type(a) is float:
+                    a = a * c
+                if type(b) is float:
+                    b = b * c
+                            
+            rv = a + t * (b - a)
+            
+            return renpy.display.core.absolute(rv)
+            
+        return [ interp(a, b, c) for a, b, c in zip(self.start, self.end, sizes) ]
+
+
+def Pan(startpos, endpos, time, child=None, repeat=False, bounce=False,
+        anim_timebase=False, style='motion', time_warp=None, **properties):
+    """
+    This is used to pan over a child displayable, which is almost
+    always an image. It works by interpolating the placement of the
+    upper-left corner of the screen, over time. It's only really
+    suitable for use with images that are larger than the screen,
+    and we don't do any cropping on the image.
+
+    @param startpos: The initial coordinates of the upper-left
+    corner of the screen, relative to the image.
+
+    @param endpos: The coordinates of the upper-left corner of the
+    screen, relative to the image, after time has elapsed.
+    
+    @param time: The time it takes to pan from startpos to endpos.
+
+    @param child: The child displayable.
+
+    @param repeat: True if we should repeat this forever.
+
+    @param bounce: True if we should bounce from the start to the end
+    to the start.
+
+    @param anim_timebase: True if we use the animation timebase, False to use the
+    displayable timebase.
+
+    @param time_warp: If not None, this is a function that takes a
+    fraction of the period (between 0.0 and 1.0), and returns a
+    new fraction of the period. Use this to warp time, applying
+    acceleration and deceleration to motions.
+
+    This can be used as a transition. See Motion for details.
+    """
+
+    x0, y0 = startpos
+    x1, y1 = endpos
+    
+    return Motion(Interpolate((-x0, -y0), (-x1, -y1)),
+                  time,
+                  child,
+                  repeat=repeat, 
+                  bounce=bounce,
+                  style=style,
+                  anim_timebase=anim_timebase,
+                  time_warp=time_warp,
+                  add_sizes=True,
+                  **properties)
+
+def Move(startpos, endpos, time, child=None, repeat=False, bounce=False,
+         anim_timebase=False, style='motion', time_warp=None, **properties):
+    """
+    This is used to pan over a child displayable relative to
+    the containing area. It works by interpolating the placement of the
+    the child, over time. 
+
+    @param startpos: The initial coordinates of the child
+    relative to the containing area.
+
+    @param endpos: The coordinates of the child at the end of the
+    move.
+    
+    @param time: The time it takes to move from startpos to endpos.
+
+    @param child: The child displayable.
+
+    @param repeat: True if we should repeat this forever.
+
+    @param bounce: True if we should bounce from the start to the end
+    to the start.
+
+    @param anim_timebase: True if we use the animation timebase, False to use the
+    displayable timebase.
+
+    @param time_warp: If not None, this is a function that takes a
+    fraction of the period (between 0.0 and 1.0), and returns a
+    new fraction of the period. Use this to warp time, applying
+    acceleration and deceleration to motions.
+
+    This can be used as a transition. See Motion for details.
+    """
+
+    return Motion(Interpolate(startpos, endpos),
+                  time,
+                  child,
+                  repeat=repeat, 
+                  bounce=bounce,
+                  anim_timebase=anim_timebase,
+                  style=style,
+                  time_warp=time_warp,
+                  add_sizes=True,
+                  **properties)
+
+
+class Revolver(object):
+
+    def __init__(self, start, end, child, around=(0.5, 0.5), cor=(0.5, 0.5), pos=None):
+        self.start = start
+        self.end = end
+        self.around = around
+        self.cor = cor
+        self.pos = pos
+        self.child = child
+        
+    def __call__(self, t, xxx_todo_changeme):
+
+        # Converts a float to an integer in the given range, passes
+        # integers through unchanged.
+        (w, h, cw, ch) = xxx_todo_changeme
+        def fti(x, r):
+            if x is None:
+                x = 0
+
+            if isinstance(x, float):
+                return int(x * r)
+            else:
+                return x
+        
+        if self.pos is None:
+            pos = self.child.get_placement()
+        else:
+            pos = self.pos
+            
+        xpos, ypos, xanchor, yanchor, _xoffset, _yoffset, _subpixel = pos
+
+        xpos = fti(xpos, w)
+        ypos = fti(ypos, h)
+        xanchor = fti(xanchor, cw)
+        yanchor = fti(yanchor, ch)
+
+        xaround, yaround = self.around
+
+        xaround = fti(xaround, w)
+        yaround = fti(yaround, h)
+
+        xcor, ycor = self.cor
+
+        xcor = fti(xcor, cw)
+        ycor = fti(ycor, ch)
+
+        angle = self.start + (self.end - self.start) * t
+        angle *= math.pi / 180
+        
+        # The center of rotation, relative to the xaround.
+        x = xpos - xanchor + xcor - xaround
+        y = ypos - yanchor + ycor - yaround
+
+        # Rotate it.
+        nx = x * math.cos(angle) - y * math.sin(angle)
+        ny = x * math.sin(angle) + y * math.cos(angle)
+
+        # Project it back.
+        nx = nx - xcor + xaround
+        ny = ny - ycor + yaround
+
+        return (renpy.display.core.absolute(nx), renpy.display.core.absolute(ny), 0, 0)
+
+
+def Revolve(start, end, time, child, around=(0.5, 0.5), cor=(0.5, 0.5), pos=None, **properties):
+
+    return Motion(Revolver(start, end, child, around=around, cor=cor, pos=pos),
+                  time,
+                  child,
+                  add_sizes=True,
+                  **properties)
+
+
+
+def zoom_render(crend, x, y, w, h, zw, zh, bilinear):
+    """
+    This creates a render that zooms its child.
+
+    `crend` - The render of the child. 
+    `x`, `y`, `w`, `h` - A rectangle inside the child.
+    `zw`, `zh` - The size the rectangle is rendered to.
+    `bilinear` - Should we be rendering in bilinear mode?
+    """
+    
+    rv = renpy.display.render.Render(zw, zh)
+
+    if zw == 0 or zh == 0 or w == 0 or h == 0:
+        return rv
+
+    
+    rv.forward = renpy.display.render.Matrix2D(w / zw, 0, 0, h / zh)
+    rv.reverse = renpy.display.render.Matrix2D(zw / w, 0, 0, zh / h)
+
+    rv.clipping = True
+    
+    rv.blit(crend, rv.reverse.transform(-x, -y))
+
+    return rv
+
+
+class ZoomCommon(renpy.display.core.Displayable):
+    def __init__(self, 
+                 time, child,
+                 end_identity=False,
+                 after_child=None,
+                 time_warp=None,
+                 bilinear=True,
+                 opaque=True,
+                 anim_timebase=False,
+                 repeat=False,
+                 style='motion',
+                 **properties):
+        """
+        @param time: The amount of time it will take to
+        interpolate from the start to the end rectange.
+
+        @param child: The child displayable.
+
+        @param after_child: If present, a second child
+        widget. This displayable will be rendered after the zoom
+        completes. Use this to snap to a sharp displayable after
+        the zoom is done.
+
+        @param time_warp: If not None, this is a function that takes a
+        fraction of the period (between 0.0 and 1.0), and returns a
+        new fraction of the period. Use this to warp time, applying
+        acceleration and deceleration to motions.
+        """
+
+        super(ZoomCommon, self).__init__(style=style, **properties)
+
+        child = renpy.easy.displayable(child)
+
+        self.time = time
+        self.child = child
+        self.repeat = repeat
+        
+        if after_child:
+            self.after_child = renpy.easy.displayable(after_child)
+        else:
+            if end_identity:
+                self.after_child = child
+            else:
+                self.after_child = None
+        
+        self.time_warp = time_warp
+        self.bilinear = bilinear
+        self.opaque = opaque
+        self.anim_timebase = anim_timebase
+        
+
+    def visit(self):
+        return [ self.child, self.after_child ]
+
+    def render(self, width, height, st, at):
+
+        if self.anim_timebase:
+            t = at
+        else:
+            t = st
+        
+        if self.time:
+            done = min(t / self.time, 1.0)
+        else:
+            done = 1.0
+
+        if self.repeat:
+            done = done % 1.0
+
+        if renpy.game.less_updates:
+            done = 1.0
+            
+        self.done = done
+
+        if self.after_child and done == 1.0:
+            return renpy.display.render.render(self.after_child, width, height, st, at)
+
+        if self.time_warp:
+            done = self.time_warp(done)
+
+        rend = renpy.display.render.render(self.child, width, height, st, at)
+
+        rx, ry, rw, rh, zw, zh = self.zoom_rectangle(done, rend.width, rend.height)
+                
+        if rx < 0 or ry < 0 or rx + rw > rend.width or ry + rh > rend.height:
+            raise Exception("Zoom rectangle %r falls outside of %dx%d parent surface." % ((rx, ry, rw, rh), rend.width, rend.height))
+
+        rv = zoom_render(rend, rx, ry, rw, rh, zw, zh, self.bilinear)
+            
+        if self.done < 1.0:
+            renpy.display.render.redraw(self, 0)
+
+        return rv
+
+    def event(self, ev, x, y, st):
+
+        if not self.time:
+            done = 1.0
+        else:
+            done = min(st / self.time, 1.0)
+            
+        if done == 1.0 and self.after_child:
+            return self.after_child.event(ev, x, y, st)
+        else:
+            return None
+
+
+class Zoom(ZoomCommon):
+
+    def __init__(self, size, start, end, time, child, **properties):
+
+        end_identity = (end == (0.0, 0.0) + size)
+
+        super(Zoom, self).__init__(time, child, end_identity=end_identity, **properties)
+
+        self.size = size
+        self.start = start
+        self.end = end
+
+    def zoom_rectangle(self, done, width, height):
+
+        rx, ry, rw, rh = [ (a + (b - a) * done) for a, b in zip(self.start, self.end) ]
+
+        return rx, ry, rw, rh, self.size[0], self.size[1]
+
+
+class FactorZoom(ZoomCommon):
+
+    def __init__(self, start, end, time, child, **properties):
+
+        end_identity = (end == 1.0)
+
+        super(FactorZoom, self).__init__(time, child, end_identity=end_identity, **properties)
+
+        self.start = start
+        self.end = end
+
+    def zoom_rectangle(self, done, width, height):
+
+        factor = self.start + (self.end - self.start) * done
+
+        return 0, 0, width, height, factor * width, factor * height
+
+
+
+class SizeZoom(ZoomCommon):
+
+    def __init__(self, start, end, time, child, **properties):
+
+        end_identity = False
+
+        super(SizeZoom, self).__init__(time, child, end_identity=end_identity, **properties)
+
+        self.start = start
+        self.end = end
+
+    def zoom_rectangle(self, done, width, height):
+
+        sw, sh = self.start
+        ew, eh = self.end
+
+        zw = sw + (ew - sw) * done
+        zh = sh + (eh - sh) * done
+        
+        return 0, 0, width, height, zw, zh
+
+
+class RotoZoom(renpy.display.core.Displayable):
+
+    transform = None
+    
+    def __init__(self,
+                 rot_start,
+                 rot_end,
+                 rot_delay,
+                 zoom_start,
+                 zoom_end,
+                 zoom_delay,
+                 child,
+                 rot_repeat=False,
+                 zoom_repeat=False,
+                 rot_bounce=False,
+                 zoom_bounce=False,
+                 rot_anim_timebase=False,
+                 zoom_anim_timebase=False,
+                 rot_time_warp=None,
+                 zoom_time_warp=None,
+                 opaque=False,
+                 style='motion',
+                 **properties):
+
+        super(RotoZoom, self).__init__(style=style, **properties)
+
+        self.rot_start = rot_start
+        self.rot_end = rot_end
+        self.rot_delay = rot_delay
+
+        self.zoom_start = zoom_start
+        self.zoom_end = zoom_end
+        self.zoom_delay = zoom_delay
+
+        self.child = renpy.easy.displayable(child)
+        
+        self.rot_repeat = rot_repeat
+        self.zoom_repeat = zoom_repeat
+
+        self.rot_bounce = rot_bounce
+        self.zoom_bounce = zoom_bounce
+        
+        self.rot_anim_timebase = rot_anim_timebase
+        self.zoom_anim_timebase = zoom_anim_timebase
+
+        self.rot_time_warp = rot_time_warp
+        self.zoom_time_warp = zoom_time_warp
+
+        self.opaque = opaque
+
+        
+    def visit(self):
+        return [ self.child ]
+
+    
+    def render(self, width, height, st, at):
+
+        if self.rot_anim_timebase:
+            rot_time = at
+        else:
+            rot_time = st
+
+        if self.zoom_anim_timebase:
+            zoom_time = at
+        else:
+            zoom_time = st
+
+        if self.rot_delay == 0:
+            rot_time = 1.0
+        else:
+            rot_time /= self.rot_delay
+
+        if self.zoom_delay == 0:
+            zoom_time = 1.0
+        else:
+            zoom_time /= self.zoom_delay
+
+        if self.rot_repeat:
+            rot_time %= 1.0
+
+        if self.zoom_repeat:
+            zoom_time %= 1.0
+
+        if self.rot_bounce:
+            rot_time *= 2
+            rot_time = min(rot_time, 2.0 - rot_time)
+
+        if self.zoom_bounce:
+            zoom_time *= 2
+            zoom_time = min(zoom_time, 2.0 - zoom_time)
+
+        if renpy.game.less_updates:
+            rot_time = 1.0
+            zoom_time = 1.0
+            
+        rot_time = min(rot_time, 1.0)
+        zoom_time = min(zoom_time, 1.0)
+        
+        if self.rot_time_warp:
+            rot_time = self.rot_time_warp(rot_time)
+
+        if self.zoom_time_warp:
+            zoom_time = self.zoom_time_warp(zoom_time)
+
+            
+        angle = self.rot_start + (1.0 * self.rot_end - self.rot_start) * rot_time
+        zoom = self.zoom_start + (1.0 * self.zoom_end - self.zoom_start) * zoom_time
+        # angle = -angle * math.pi / 180
+
+        zoom = max(zoom, 0.001) 
+        
+        if self.transform is None:
+            self.transform = Transform(self.child)
+            
+        self.transform.rotate = angle
+        self.transform.zoom = zoom
+
+        rv = renpy.display.render.render(self.transform, width, height, st, at)
+        
+        if rot_time <= 1.0 or zoom_time <= 1.0:
+            renpy.display.render.redraw(self.transform, 0)
+
+        return rv
+
+
+# For compatibility with old games.
+renpy.display.layout.Transform = Transform
+renpy.display.layout.RotoZoom = RotoZoom
+renpy.display.layout.SizeZoom = SizeZoom
+renpy.display.layout.FactorZoom = FactorZoom
+renpy.display.layout.Zoom = Zoom
+renpy.display.layout.Revolver = Revolver
+renpy.display.layout.Motion = Motion
+renpy.display.layout.Interpolate = Interpolate
+
+# Leave these functions around - they might have been pickled somewhere.
+renpy.display.layout.Revolve = Revolve # function
+renpy.display.layout.Move = Move # function
+renpy.display.layout.Pan = Pan # function
diff --git a/unrpyc/renpy/display/movetransition.py b/unrpyc/renpy/display/movetransition.py
new file mode 100644
index 0000000..1b3bdd7
--- /dev/null
+++ b/unrpyc/renpy/display/movetransition.py
@@ -0,0 +1,640 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# NOTE:
+# Transitions need to be able to work even when old_widget and new_widget
+# are None, at least to the point of making it through __init__. This is
+# so that prediction of images works.
+
+import renpy.display
+
+# Utility function used by MoveTransition et al.
+def position(d):
+
+    xpos, ypos, xanchor, yanchor, _xoffset, _yoffset, _subpixel = d.get_placement()
+
+    if xpos is None:
+        xpos = 0
+    if ypos is None:
+        ypos = 0
+    if xanchor is None:
+        xanchor = 0
+    if yanchor is None:
+        yanchor = 0
+
+    return xpos, ypos, xanchor, yanchor
+
+def offsets(d):
+    
+    _xpos, _ypos, _xanchor, _yanchor, xoffset, yoffset, _subpixel = d.get_placement()
+
+    if renpy.config.movetransition_respects_offsets:
+        return { 'xoffset' : xoffset, 'yoffset' : yoffset }
+    else:
+        return { }
+    
+    
+# These are used by MoveTransition.
+def MoveFactory(pos1, pos2, delay, d, **kwargs):
+    if pos1 == pos2:
+        return d
+
+    return renpy.display.motion.Move(pos1, pos2, delay, d, **kwargs)
+
+def default_enter_factory(pos, delay, d, **kwargs):
+    return d
+
+def default_leave_factory(pos, delay, d, **kwargs):
+    return None
+
+# These can be used to move things in and out of the screen.
+def MoveIn(pos, pos1, delay, d, **kwargs):
+
+    def aorb(a, b):
+        if a is None:
+            return b
+        return a
+
+    pos = tuple([aorb(a, b) for a, b in zip(pos, pos1)])
+    return renpy.display.motion.Move(pos, pos1, delay, d, **kwargs)
+
+def MoveOut(pos, pos1, delay, d, **kwargs):
+
+    def aorb(a, b):
+        if a is None:
+            return b
+        return a
+
+    pos = tuple([aorb(a, b) for a, b in zip(pos, pos1)])
+    return renpy.display.motion.Move(pos1, pos, delay, d, **kwargs)
+
+def ZoomInOut(start, end, pos, delay, d, **kwargs):
+
+    xpos, ypos, xanchor, yanchor = pos
+
+    FactorZoom = renpy.display.motion.FactorZoom
+    
+    if end == 1.0:
+        return FactorZoom(start, end, delay, d, after_child=d, opaque=False,
+                          xpos=xpos, ypos=ypos, xanchor=xanchor, yanchor=yanchor, **kwargs)
+    else:
+        return FactorZoom(start, end, delay, d, opaque=False,
+                          xpos=xpos, ypos=ypos, xanchor=xanchor, yanchor=yanchor, **kwargs)
+
+def RevolveInOut(start, end, pos, delay, d, **kwargs):
+    return renpy.display.motion.Revolve(start, end, delay, d, pos=pos, **kwargs)
+    
+    
+def OldMoveTransition(delay, old_widget=None, new_widget=None, factory=None, enter_factory=None, leave_factory=None, old=False, layers=[ 'master' ]):
+    """
+    Returns a transition that attempts to find images that have changed
+    position, and moves them from the old position to the new transition, taking
+    delay seconds to complete the move.
+
+    If `factory` is given, it is expected to be a function that takes as
+    arguments: an old position, a new position, the delay, and a
+    displayable, and to return a displayable as an argument. If not
+    given, the default behavior is to move the displayable from the
+    starting to the ending positions. Positions are always given as
+    (xpos, ypos, xanchor, yanchor) tuples.
+
+    If `enter_factory` or `leave_factory` are given, they are expected
+    to be functions that take as arguments a position, a delay, and a
+    displayable, and return a displayable. They are applied to
+    displayables that are entering or leaving the scene,
+    respectively. The default is to show in place displayables that
+    are entering, and not to show those that are leaving.
+
+    If `old` is True, then factory moves the old displayable with the
+    given tag. Otherwise, it moves the new displayable with that
+    tag.
+
+    `layers` is a list of layers that the transition will be applied
+    to.
+
+    Images are considered to be the same if they have the same tag, in
+    the same way that the tag is used to determine which image to
+    replace or to hide. They are also considered to be the same if
+    they have no tag, but use the same displayable.
+
+    Computing the order in which images are displayed is a three-step
+    process. The first step is to create a list of images that
+    preserves the relative ordering of entering and moving images. The
+    second step is to insert the leaving images such that each leaving
+    image is at the lowest position that is still above all images
+    that were below it in the original scene. Finally, the list 
+    is sorted by zorder, to ensure no zorder violations occur.
+    
+    If you use this transition to slide an image off the side of the
+    screen, remember to hide it when you are done. (Or just use
+    a leave_factory.)
+    """
+
+    if factory is None:
+        factory = MoveFactory
+
+    if enter_factory is None:
+        enter_factory = default_enter_factory
+
+    if leave_factory is None:
+        leave_factory = default_leave_factory
+
+    use_old = old
+    
+    def merge_slide(old, new):
+
+        # If new does not have .layers or .scene_list, then we simply
+        # insert a move from the old position to the new position, if
+        # a move occured.
+            
+        if (not isinstance(new, renpy.display.layout.MultiBox)
+            or (new.layers is None and new.layer_name is None)):
+
+            if use_old:
+                child = old
+            else:
+                child = new
+
+            old_pos = position(old)
+            new_pos = position(new)
+                
+            if old_pos != new_pos:
+                return factory(old_pos,
+                               new_pos,
+                               delay,
+                               child,
+                               **offsets(child)
+                               )
+            
+            else:
+                return child
+
+        # If we're in the layers_root widget, merge the child widgets
+        # for each layer.
+        if new.layers:
+
+            assert old.layers
+
+            rv = renpy.display.layout.MultiBox(layout='fixed')
+            rv.layers = { }
+
+            for layer in renpy.config.layers:
+
+                f = new.layers[layer]
+
+                if (isinstance(f, renpy.display.layout.MultiBox) 
+                    and layer in layers
+                    and f.scene_list is not None):
+                    
+                    f = merge_slide(old.layers[layer], new.layers[layer])
+
+                rv.layers[layer] = f
+                rv.add(f)
+
+            return rv
+
+        # Otherwise, we recompute the scene list for the two widgets, merging
+        # as appropriate.
+
+        # Wraps the displayable found in SLE so that the various timebases
+        # are maintained.
+        def wrap(sle):
+            return renpy.display.layout.AdjustTimes(sle.displayable, sle.show_time, sle.animation_time)
+        
+        def tag(sle):
+            return sle.tag or sle.displayable
+
+        def merge(sle, d):
+            rv = sle.copy()
+            rv.show_time = 0
+            rv.displayable = d
+            return rv
+
+        def entering(sle):
+            new_d = wrap(new_sle)
+            move = enter_factory(position(new_d), delay, new_d, **offsets(new_d))
+
+            if move is None:
+                return
+
+            rv_sl.append(merge(new_sle, move))
+
+        def leaving(sle):
+            old_d = wrap(sle)
+            move = leave_factory(position(old_d), delay, old_d, **offsets(old_d))
+
+            if move is None:
+                return
+
+            move = renpy.display.layout.IgnoresEvents(move)
+            rv_sl.append(merge(old_sle, move))
+
+
+        def moving(old_sle, new_sle):
+            old_d = wrap(old_sle)
+            new_d = wrap(new_sle)
+
+            if use_old:
+                child = old_d
+            else:
+                child = new_d
+                
+            move = factory(position(old_d), position(new_d), delay, child, **offsets(child))
+            if move is None:
+                return
+
+            rv_sl.append(merge(new_sle, move))
+
+
+        # The old, new, and merged scene_lists.
+        old_sl = old.scene_list[:]
+        new_sl = new.scene_list[:]
+        rv_sl = [ ]
+        
+
+        # A list of tags in old_sl, new_sl, and rv_sl.
+        old_map = dict((tag(i), i) for i in old_sl if i is not None)
+        new_tags = set(tag(i) for i in new_sl if i is not None)
+        rv_tags = set()
+        
+        while old_sl or new_sl:
+
+            # If we have something in old_sl, then 
+            if old_sl:
+
+                old_sle = old_sl[0]
+                old_tag = tag(old_sle)
+
+                # If the old thing has already moved, then remove it.
+                if old_tag in rv_tags:
+                    old_sl.pop(0)
+                    continue
+
+                # If the old thing does not match anything in new_tags,
+                # have it enter.
+                if old_tag not in new_tags:
+                    leaving(old_sle)
+                    rv_tags.add(old_tag)
+                    old_sl.pop(0)
+                    continue
+
+
+            # Otherwise, we must have something in new_sl. We want to
+            # either move it or have it enter.
+
+            new_sle = new_sl.pop(0)
+            new_tag = tag(new_sle)
+
+            # If it exists in both, move.
+            if new_tag in old_map:
+                old_sle = old_map[new_tag]
+
+                moving(old_sle, new_sle)
+                rv_tags.add(new_tag)
+                continue
+
+            else:
+                entering(new_sle)
+                rv_tags.add(new_tag)
+                continue
+
+        # Sort everything by zorder, to ensure that there are no zorder
+        # violations in the result.
+        rv_sl.sort(key=lambda a : a.zorder)
+            
+        layer = new.layer_name
+        rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **renpy.game.interface.layer_properties[layer])
+        rv.append_scene_list(rv_sl)
+        rv.layer_name = layer
+
+        return rv
+
+
+    # This calls merge_slide to actually do the merging.
+
+    rv = merge_slide(old_widget, new_widget)
+    rv.delay = delay # W0201
+
+    return rv
+
+##############################################################################
+# New Move Transition (since 6.14)
+
+
+class MoveInterpolate(renpy.display.core.Displayable):
+    """
+    This displayable has two children. It interpolates between the positions 
+    of its two children to place them on the screen.
+    """
+
+    def __init__(self, delay, old, new, use_old, time_warp):
+        super(MoveInterpolate, self).__init__()
+        
+        # The old and new displayables.
+        self.old = old
+        self.new = new
+        
+        # Should we display the old displayable?
+        self.use_old = False
+        
+        # Time warp function or None.
+        self.time_warp = time_warp
+        
+        # The width of the screen.
+        self.screen_width = 0
+        self.screen_height = 0
+        
+        # The width of the selected child.
+        self.child_width = 0
+        self.child_height = 0
+        
+        # The delay and st.
+        self.delay = delay
+        self.st = 0
+        
+    def render(self, width, height, st, at):
+        self.screen_width = width
+        self.screen_height = height
+
+        old_r = renpy.display.render.render(self.old, width, height, st, at)
+        new_r = renpy.display.render.render(self.new, width, height, st, at)
+        
+        if self.use_old:
+            cr = old_r
+        else:
+            cr = new_r
+            
+        self.child_width, self.child_height = cr.get_size()
+        self.st = st
+
+        if self.st < self.delay:
+            renpy.display.render.redraw(self, 0)
+        
+        return cr
+        
+    def child_placement(self, child):
+
+        def based(v, base):
+            if v is None:
+                return 0
+            elif isinstance(v, int):
+                return v
+            elif isinstance(v, renpy.display.core.absolute):
+                return v
+            else:
+                return v * base
+        
+        xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel = child.get_placement()
+        
+        xpos = based(xpos, self.screen_width)
+        ypos = based(ypos, self.screen_height)
+        xanchor = based(xanchor, self.child_width)
+        yanchor = based(yanchor, self.child_height)
+    
+        return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+    
+    def get_placement(self):
+        
+        if self.st > self.delay:
+            done = 1.0
+        else:
+            done = self.st / self.delay
+        
+        if self.time_warp is not None:
+            done = self.time_warp(done)
+        
+        absolute = renpy.display.core.absolute
+        
+        def I(a, b):
+            return absolute(a + done * (b - a))
+        
+        old_xpos, old_ypos, old_xanchor, old_yanchor, old_xoffset, old_yoffset, old_subpixel = self.child_placement(self.old)
+        new_xpos, new_ypos, new_xanchor, new_yanchor, new_xoffset, new_yoffset, new_subpixel = self.child_placement(self.new)
+        
+        xpos = I(old_xpos, new_xpos)
+        ypos = I(old_ypos, new_ypos)
+        xanchor = I(old_xanchor, new_xanchor)
+        yanchor = I(old_yanchor, new_yanchor)
+        xoffset = I(old_xoffset, new_xoffset)
+        yoffset = I(old_yoffset, new_yoffset)
+        subpixel = old_subpixel or new_subpixel
+
+        return xpos, ypos, xanchor, yanchor, xoffset, yoffset, subpixel
+        
+
+def MoveTransition(delay, old_widget=None, new_widget=None, enter=None, leave=None, old=False, layers=[ 'master' ], time_warp=None, enter_time_warp=None, leave_time_warp=None):
+    """
+    :doc: transition function
+    :args: (delay, enter=None, leave=None, old=False, layers=['master'], time_warp=None, enter_time_warp=None, leave_time_warp=None)
+    :name: MoveTransition
+    
+    Returns a transition that interpolates the position of images (with the
+    same tag) in the old and new scenes.
+    
+    `delay`
+        The time it takes for the interpolation to finish.
+        
+    `enter`
+        If not None, images entering the scene will also be moved. The value
+        of `enter` should be a transform that is applied to the image to 
+        get its starting position.
+         
+    `leave`
+        If not None, images leaving the scene will also be move. The value 
+        of `leave` should be a transform that is applied to the image to
+        get its ending position.
+        
+    `old`
+        If true, the old image will be used in preference to the new one.
+        
+    `layers`
+        A list of layers that moves are applied to.
+        
+    `time_warp`
+        A time warp function that's applied to the interpolation. This 
+        takes a number between 0.0 and 1.0, and should return a number in
+        the same range.
+        
+    `enter_time_warp`
+        A time warp function that's applied to images entering the scene.
+
+    `enter_time_warp`
+        A time warp function that's applied to images leaving the scene.
+
+    """
+
+    use_old = old
+    
+    def merge_slide(old, new):
+
+        # If new does not have .layers or .scene_list, then we simply
+        # insert a move from the old position to the new position, if
+        # a move occured.
+            
+        if (not isinstance(new, renpy.display.layout.MultiBox)
+            or (new.layers is None and new.layer_name is None)):
+
+            if old is new:
+                return new
+            else:
+                return MoveInterpolate(delay, old, new, use_old, time_warp)
+
+
+        # If we're in the layers_root widget, merge the child widgets
+        # for each layer.
+        if new.layers:
+
+            assert old.layers
+
+            rv = renpy.display.layout.MultiBox(layout='fixed')
+
+            for layer in renpy.config.layers:
+
+                f = new.layers[layer]
+
+                if (isinstance(f, renpy.display.layout.MultiBox) 
+                    and layer in layers
+                    and f.scene_list is not None):
+                    
+                    f = merge_slide(old.layers[layer], new.layers[layer])
+
+                rv.add(f)
+
+            return rv
+
+        # Otherwise, we recompute the scene list for the two widgets, merging
+        # as appropriate.
+
+        # Wraps the displayable found in SLE so that the various timebases
+        # are maintained.
+        def wrap(sle):
+            return renpy.display.layout.AdjustTimes(sle.displayable, sle.show_time, sle.animation_time)
+        
+        def tag(sle):
+            return sle.tag or sle.displayable
+
+        def merge(sle, d):
+            rv = sle.copy()
+            rv.show_time = 0
+            rv.displayable = d
+            return rv
+
+        def entering(sle):
+
+            if not enter:
+                return
+            
+            new_d = wrap(new_sle)
+            move = MoveInterpolate(delay, enter(new_d), new_d, False, enter_time_warp)
+            rv_sl.append(merge(new_sle, move))
+
+        def leaving(sle):
+            
+            if not leave:
+                return
+            
+            old_d = wrap(sle)
+            move = MoveInterpolate(delay, old_d, leave(old_d), True, leave_time_warp)
+            move = renpy.display.layout.IgnoresEvents(move)
+            rv_sl.append(merge(old_sle, move))
+
+
+        def moving(old_sle, new_sle):
+
+            if old_sle.displayable is new_sle.displayable:
+                rv_sl.append(new_sle)
+                return
+            
+            old_d = wrap(old_sle)
+            new_d = wrap(new_sle)
+
+            move = MoveInterpolate(delay, old_d, new_d, use_old, time_warp)
+
+            rv_sl.append(merge(new_sle, move))
+
+
+        # The old, new, and merged scene_lists.
+        old_sl = old.scene_list[:]
+        new_sl = new.scene_list[:]
+        rv_sl = [ ]
+
+        # A list of tags in old_sl, new_sl, and rv_sl.
+        old_map = dict((tag(i), i) for i in old_sl if i is not None)
+        new_tags = set(tag(i) for i in new_sl if i is not None)
+        rv_tags = set()
+        
+        while old_sl or new_sl:
+
+            # If we have something in old_sl, then 
+            if old_sl:
+
+                old_sle = old_sl[0]
+                old_tag = tag(old_sle)
+
+                # If the old thing has already moved, then remove it.
+                if old_tag in rv_tags:
+                    old_sl.pop(0)
+                    continue
+
+                # If the old thing does not match anything in new_tags,
+                # have it enter.
+                if old_tag not in new_tags:
+                    leaving(old_sle)
+                    rv_tags.add(old_tag)
+                    old_sl.pop(0)
+                    continue
+
+
+            # Otherwise, we must have something in new_sl. We want to
+            # either move it or have it enter.
+
+            new_sle = new_sl.pop(0)
+            new_tag = tag(new_sle)
+
+            # If it exists in both, move.
+            if new_tag in old_map:
+                old_sle = old_map[new_tag]
+
+                moving(old_sle, new_sle)
+                rv_tags.add(new_tag)
+                continue
+
+            else:
+                entering(new_sle)
+                rv_tags.add(new_tag)
+                continue
+
+        # Sort everything by zorder, to ensure that there are no zorder
+        # violations in the result.
+        rv_sl.sort(key=lambda a : a.zorder)
+            
+        layer = new.layer_name
+        rv = renpy.display.layout.MultiBox(layout='fixed', focus=layer, **renpy.game.interface.layer_properties[layer])
+        rv.append_scene_list(rv_sl)
+
+        return rv
+
+    # Call merge_slide to actually do the merging.
+    rv = merge_slide(old_widget, new_widget)
+    rv.delay = delay
+
+    return rv
+
diff --git a/unrpyc/renpy/display/particle.py b/unrpyc/renpy/display/particle.py
new file mode 100644
index 0000000..cf52417
--- /dev/null
+++ b/unrpyc/renpy/display/particle.py
@@ -0,0 +1,615 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This code supports sprite and particle animation.
+
+from renpy.display.render import render, BLIT
+
+import renpy.display
+import random
+
+
+class SpriteCache(renpy.object.Object):
+    """
+    This stores information about a displayble, including the identity
+    of the displayable, and when it was first displayed. It is also
+    responsible for caching the displayable surface, so it doesn't
+    need to be re-rendered.
+    """
+
+    # Private Fields:
+    #
+    # child - The child displayable.
+    # 
+    # st - The shown time when this was first displayed, or None if it hasn't
+    # been rendered.
+    #
+    # render - The render of child.
+    #
+    # If true, then the render is simple enough it can just be appended to
+    # the manager's render's children list.
+    
+class Sprite(renpy.object.Object):
+    """
+    :doc: sprites class
+    
+    This represents a sprite that is managed by the SpriteManager. It contains
+    fields that control the placement of the sprite on the screen. Sprites
+    should not be created directly. Instead, they should be created by
+    calling :meth:`SpriteManager.create`.
+
+    The fields of a sprite object are:
+
+    `x`, `y`
+        The x and y coordinates of the upper-left corner of the sprite,
+        relative to the SpriteManager.
+
+    `zorder`
+        An integer that's used to control the order of this sprite in the
+        relative to the other sprites in the SpriteManager. The larger the
+        number is, the closer to the viewer the sprite is.
+
+    `events`
+        If True, then events are passed to child. If False, the default, 
+        the children igore events (and hence don't spend time processing
+        them).     
+
+    The methods of a Sprite object are:
+        """
+
+    # Fields:
+    #
+    # child - the displayable that is the child of this sprite.
+    # cache - the SpriteCache of child.
+    # live - True if this sprite is still alive.
+    # manager - A reference to the SpriteManager.
+
+    def set_child(self, d):
+        """
+        :doc: sprites method
+
+        Changes the Displayable associated with this sprite to `d`.
+        """
+
+        id_d = id(d)
+                
+        sc = self.manager.displayable_map.get(id_d, None)
+        if sc is None:
+            d = renpy.easy.displayable(d)
+
+            sc = SpriteCache()
+            sc.render = None
+            sc.child = d
+            sc.st = None
+            
+            self.manager.displayable_map[id_d] = sc
+
+        self.cache = sc
+            
+    def destroy(self):
+        """
+        :doc: sprites method
+
+        Destroys this sprite, preventing it from being displayed and
+        removing it from the SpriteManager.
+        """
+
+        self.manager.dead_child = True
+        self.live = False
+        self.events = False
+        
+    
+
+class SpriteManager(renpy.display.core.Displayable):
+    """
+    :doc: sprites class
+    
+    This displayable manages a collection of sprites, and displays
+    them at the fastest speed possible.
+    """
+    
+    def __init__(self, update=None, event=None, predict=None, ignore_time=False, **properties):
+        """
+        `update`
+            If not None, a function that is called each time a sprite
+            is rendered by this sprite manager. It is called with one
+            argument, the time in seconds since this sprite manager
+            was first displayed.  It is expected to return the number
+            of seconds until the function is called again, and the
+            SpriteManager is rendered again.
+
+        `event`
+            If not None, a function that is called when an event occurs.
+            It takes as arguments:
+            * A pygame event object.
+            * The x coordinate of the event.
+            * The y coordinate of the event.
+            * The time since the sprite manager was first shown.
+            If it returns a non-None value, the interaction ends, and
+            that value is returned.
+
+        `predict`
+            If not None, a function that returns a list of
+            displayables. These displayables are predicted when the
+            sprite manager is.
+            
+        `ignore_time`
+            If True, then time is ignored when rendering displayables. This
+            should be used when the sprite manager is used with a relatively
+            small pool of images, and those images do not change over time.
+            This should only be used with a small number of displayables, as
+            it will keep all displayables used in memory for the life of the
+            SpriteManager.
+
+        After being rendered once (before the `update` function is called),
+        SpriteManagers have the following fields:
+
+        `width`, `height`
+        
+             The width and height of this SpriteManager, in pixels.
+            
+            
+        SpriteManagers have the following methods:
+        """
+
+        super(SpriteManager, self).__init__(self, **properties)
+
+        self.update_function = update
+        self.event_function = event
+        self.predict_function = predict
+        self.ignore_time = ignore_time
+        
+        # A map from a displayable to the SpriteDisplayable object
+        # representing that displayable.
+        self.displayable_map = { }
+
+        # A list of children of this displayable, in zorder. (When sorted.)
+        # This is a list of Sprites.
+        self.children = [ ]
+
+        # True if at least one child has been killed.
+        self.dead_child = False
+
+        # True if at least one child responds to events.
+        self.events = False
+
+        # The width and height.
+        self.width = None
+        self.height = None
+        
+    def create(self, d):
+        """
+        :doc: sprites method
+
+        Creates a new Sprite for the displayable `d`, and adds it to this
+        SpriteManager.
+        """
+
+        id_d = id(d)
+        
+        sc = self.displayable_map.get(id_d, None)
+        if sc is None:
+            d = renpy.easy.displayable(d)
+
+            sc = SpriteCache()
+            sc.render = None
+            sc.child = d
+            sc.st = None
+            self.displayable_map[id_d] = sc
+
+        s = Sprite()
+        s.x = 0
+        s.y = 0
+        s.zorder = 0
+        s.cache = sc
+        s.live = True
+        s.manager = self
+        s.events = False
+        
+        self.children.append(s)
+
+        return s
+
+    def predict_one(self):
+        if self.predict_function is not None:
+            for i in self.predict_function():
+                renpy.display.predict.displayable(i)
+                
+    
+    def redraw(self, delay=0):
+        """
+        :doc: sprite method
+
+        Causes this SpriteManager to be redrawn in `delay` seconds.
+        """
+
+        renpy.display.render.redraw(self, delay)
+        
+    def render(self, width, height, st, at):
+
+        self.width = width
+        self.height = height
+
+        if self.update_function is not None:
+
+            redraw = self.update_function(st)
+
+            if redraw is not None:
+                renpy.display.render.redraw(self, redraw)
+            
+        if not self.ignore_time:
+            self.displayable_map.clear()
+        
+        if self.dead_child:
+            self.children = [ i for i in self.children if i.live ]
+
+        self.children.sort(key=lambda sc:sc.zorder)
+
+        caches = [ ]
+
+        rv = renpy.display.render.Render(width, height)
+
+        events = False
+        
+        for i in self.children:
+
+            events |= i.events
+            
+            cache = i.cache
+            r = i.cache.render
+            if cache.render is None:
+                if cache.st is None:
+                    cache.st = st
+
+                cst = st - cache.st
+
+                cache.render = r = render(cache.child, width, height, cst, cst)
+                cache.fast = (r.operation == BLIT) and (r.forward is None) and (r.alpha == 1.0)
+                rv.depends_on(r)
+                
+                caches.append(cache)
+                
+
+            if cache.fast:
+                for child, xo, yo, _focus, _main in r.children:
+                    rv.children.append((child,
+                                        xo + i.x,
+                                        yo + i.y,
+                                        False,
+                                        False))
+
+            else:
+                rv.subpixel_blit(r, (i.x, i.y))
+
+        for i in caches:
+            i.render = None
+                
+        return rv
+                
+    def event(self, ev, x, y, st):
+        for i in range(len(self.children) -1, -1, -1): 
+            s = self.children[i]
+            
+            if s.events:
+                rv = s.cache.child.event(ev, x - s.x, y - s.y, st - s.cache.st)
+                if rv is not None:
+                    return rv
+
+        if self.event_function is not None:
+            return self.event_function(ev, x, y, st)
+        else:
+            return None
+
+    def visit(self):
+        rv = [ ]
+
+        try:
+            if self.predict_function:
+                pl = self.predict_function()
+                for i in pl:
+                    i = renpy.easy.displayable(i)
+                    rv.append(i)
+        except:
+            pass
+
+        return rv
+    
+    def destroy_all(self):
+        self.children = [ ]
+    
+
+class Particles(renpy.display.core.Displayable, renpy.python.NoRollback):
+    """
+    Supports particle motion, using the old API.
+    """
+
+    __version__ = 1
+    
+    nosave = [ 'particles' ]
+
+    def after_upgrade(self, version):
+        if version < 1:
+            self.sm = SpriteManager(update=self.update_callback, predict=self.predict_callback)
+        
+    def after_setstate(self):
+        self.particles = None
+
+    def __init__(self, factory, **properties):
+        """
+        @param factory: A factory object.
+        """
+
+        super(Particles, self).__init__(**properties)
+
+        self.sm = SpriteManager(update=self.update_callback, predict=self.predict_callback)
+
+        self.factory = factory
+        self.particles = None
+
+    def update_callback(self, st):
+
+        particles = self.particles
+
+        if st == 0 or particles is None:
+            self.sm.destroy_all()
+            particles = [ ]
+
+        add_parts = self.factory.create(particles, st)
+            
+        new_particles = [ ]
+            
+        for sprite, p in particles:
+            update = p.update(st)
+
+            if update is None:
+                sprite.destroy()
+                continue
+
+            x, y, _t, d = update
+            
+            if d is not sprite.cache.child:
+                sprite.set_child(d)
+
+            sprite.x = x
+            sprite.y = y
+            
+            new_particles.append((sprite, p))
+
+        if add_parts:
+            for p in add_parts:
+                update = p.update(st)
+
+                if update is None:
+                    continue
+                
+                x, y, _t, d = update
+
+                if d is None:
+                    continue
+
+                sprite = self.sm.create(d)
+                sprite.x = x
+                sprite.y = y
+
+                new_particles.append((sprite, p))
+            
+        self.particles = new_particles
+        
+        return 0
+            
+    def predict_callback(self):
+        return self.factory.predict()            
+        
+    def render(self, w, h, st, at):
+        return renpy.display.render.render(self.sm, w, h, st, at)
+    
+class SnowBlossomFactory(renpy.python.NoRollback):
+
+    rotate = False
+    
+    def __setstate__(self, state):
+        self.start = 0
+        vars(self).update(state)
+        self.init()
+
+    def __init__(self, image, count, xspeed, yspeed, border, start, fast, rotate=False):
+        self.image = renpy.easy.displayable(image)
+        self.count = count 
+        self.xspeed = xspeed
+        self.yspeed = yspeed
+        self.border = border        
+        self.start = start
+        self.fast = fast
+        self.rotate = rotate
+        self.init()
+
+    def init(self):
+        self.starts = [ random.uniform(0, self.start) for _i in range(0, self.count) ] # W0201
+        self.starts.append(self.start)
+        self.starts.sort()
+    
+    def create(self, particles, st):
+
+        def ranged(n):
+            if isinstance(n, tuple):
+                return random.uniform(n[0], n[1])
+            else:
+                return n
+
+        if not particles and self.fast:
+            rv = [ ]
+
+            for _i in range(0, self.count):
+                rv.append(SnowBlossomParticle(self.image,
+                                              ranged(self.xspeed),
+                                              ranged(self.yspeed),
+                                              self.border,
+                                              st,
+                                              random.uniform(0, 100),
+                                              fast=True,
+                                              rotate=self.rotate))
+            return rv
+            
+        
+        if particles is None or len(particles) < self.count:
+
+            # Check to see if we have a particle ready to start. If not,
+            # don't start it.
+            if particles and st < self.starts[len(particles)]:
+                return None
+
+            return [ SnowBlossomParticle(self.image,
+                                         ranged(self.xspeed),
+                                         ranged(self.yspeed),
+                                         self.border,
+                                         st,
+                                         random.uniform(0, 100),
+                                         fast=False,
+                                         rotate=self.rotate) ]
+
+    def predict(self):
+        return [ self.image ]
+    
+
+class SnowBlossomParticle(renpy.python.NoRollback):
+
+    def __init__(self, image, xspeed, yspeed, border, start, offset, fast, rotate):
+
+        # safety.
+        if yspeed == 0:
+            yspeed = 1
+
+        self.image = image
+        self.xspeed = xspeed
+        self.yspeed = yspeed
+        self.border = border
+        self.start = start
+        self.offset = offset
+        self.rotate = rotate
+        
+        
+        if not rotate:
+            sh = renpy.config.screen_height
+            sw = renpy.config.screen_width
+        else:
+            sw = renpy.config.screen_height
+            sh = renpy.config.screen_width
+            
+            
+        if self.yspeed > 0:
+            self.ystart = -border
+        else:
+            self.ystart = sh + border
+        
+
+        travel_time = (2.0 * border + sh) / abs(yspeed)
+
+        xdist = xspeed * travel_time
+
+        x0 = min(-xdist, 0)
+        x1 = max(sw + xdist, sw)
+
+        self.xstart = random.uniform(x0, x1)
+
+        if fast:
+            self.ystart = random.uniform(-border, sh + border)
+            self.xstart = random.uniform(0, sw)
+
+    def update(self, st):
+        to = st - self.start
+
+        xpos = self.xstart + to * self.xspeed
+        ypos = self.ystart + to * self.yspeed
+
+        if not self.rotate:
+            sh = renpy.config.screen_height
+        else:
+            sh = renpy.config.screen_width
+        
+        if ypos > sh + self.border:
+            return None
+
+        if ypos < -self.border:
+            return None
+
+        if not self.rotate:
+            return int(xpos), int(ypos), to + self.offset, self.image
+        else:
+            return int(ypos), int(xpos), to + self.offset, self.image
+        
+def SnowBlossom(d,
+                count=10,
+                border=50,
+                xspeed=(20, 50),
+                yspeed=(100, 200),
+                start=0,
+                fast=False,
+                horizontal=False):
+
+    """
+    :doc: sprites_extra
+
+    The snowblossom effect moves multiple instances of a sprite up,
+    down, left or right on the screen. When a sprite leaves the screen, it
+    is returned to the start.
+
+    `d`
+        The displayable to use for the sprites.
+
+    `border`
+        The size of the border of the screen. The sprite is considered to be
+        on the screen until it clears the border, ensuring that sprites do
+        not disappear abruptly.
+
+    `xspeed`, `yspeed`
+        The speed at which the sprites move, in the horizontal and vertical
+        directions, respectively. These can be a single number or a tuple of
+        two numbers. In the latter case, each particle is assigned a random
+        speed between the two numbers. The speeds can be positive or negative,
+        as long as the second number in a tuple is larger than the first.
+
+    `start`
+        The delay, in seconds, before each particle is added. This can be
+        allows the particles to start at the top of the screen, while not
+        looking like a "wave" effect.
+
+    `fast`
+        If true, particles start in the center of the screen, rather than
+        only at the edges.
+
+    `horizontal`
+        If true, particles appear on the left or right side of the screen,
+        rather than the top or bottom.
+        """
+    
+    # If going horizontal, swap the xspeed and the yspeed.
+    if horizontal:
+        xspeed, yspeed = yspeed, xspeed
+
+    return Particles(SnowBlossomFactory(image=d,
+                                        count=count,
+                                        border=border,
+                                        xspeed=xspeed,
+                                        yspeed=yspeed,
+                                        start=start,
+                                        fast=fast,
+                                        rotate=horizontal))
+
diff --git a/unrpyc/renpy/display/pgrender.py b/unrpyc/renpy/display/pgrender.py
new file mode 100644
index 0000000..32d1df8
--- /dev/null
+++ b/unrpyc/renpy/display/pgrender.py
@@ -0,0 +1,167 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This module wraps the pygame surface class (and associated functions). It 
+# ensures that returned surfaces have a 2px border around them.
+
+import sys
+import pygame
+import renpy.display
+
+# Sample surfaces, with and without alpha.
+sample_alpha = None
+sample_noalpha = None
+
+def set_rgba_masks():
+    """
+    This rebuilds the sample surfaces, to ones that use the given
+    masks.
+    """
+
+    # Annoyingly, the value for the big mask seems to vary from
+    # platform to platform. So we read it out of a surface.
+    
+    global sample_alpha
+    global sample_noalpha
+
+    # Create a sample surface.
+    s = pygame.Surface((10, 10), 0, 32)
+    sample_alpha = s.convert_alpha()
+
+    # Sort the components by absolute value.
+    masks = list(sample_alpha.get_masks())
+    masks.sort(key=lambda a : abs(a))
+
+    # Choose the masks.
+    if sys.byteorder == 'big':
+        masks = ( masks[3], masks[2], masks[1], masks[0] )
+    else:
+        masks = ( masks[0], masks[1], masks[2], masks[3] )
+
+    # Create the sample surface.
+    sample_alpha = pygame.Surface((10, 10), 0, 32, masks)
+    sample_noalpha = pygame.Surface((10, 10), 0, 32, masks[:3] + (0,))
+    
+
+class Surface(pygame.Surface):
+    """
+    This allows us to wrap around pygame's surface, to change
+    its mode, as necessary.
+    """
+
+    opaque = False
+    
+    def is_opaque(self):
+        return self.opaque
+    
+    def convert_alpha(self, surface=None):
+        return copy_surface_unscaled(self, True)
+
+    def convert(self, surface=None):
+        return copy_surface(self, False)
+
+    def copy(self):
+        return copy_surface(self, self)
+
+    def subsurface(self, rect):
+        rv = pygame.Surface.subsurface(self, rect)
+        return rv
+
+def surface(xxx_todo_changeme, alpha):
+    """
+    Constructs a new surface. The allocated surface is actually a subsurface
+    of a surface that has a 2 pixel border in all directions.
+
+    `alpha` - True if the new surface should have an alpha channel.
+    """
+    (width, height) = xxx_todo_changeme
+    if isinstance(alpha, pygame.Surface):
+        alpha = alpha.get_masks()[3]
+    
+    if alpha:
+        sample = sample_alpha
+    else:
+        sample = sample_noalpha
+
+    # We might not have initialized properly yet. This is enough
+    # to get us underway.
+    if sample is None:
+        sample = pygame.Surface((4, 4), pygame.SRCALPHA, 32)
+        
+    surf = Surface((width + 4, height + 4), 0, sample)
+    return surf.subsurface((2, 2, width, height)) # E1101
+
+surface_unscaled = surface
+
+def copy_surface(surf, alpha=True):
+    """
+    Creates a copy of the surface.
+    """
+    
+    rv = surface_unscaled(surf.get_size(), alpha)
+    renpy.display.accelerator.nogil_copy(surf, rv) # @UndefinedVariable
+    return rv
+
+copy_surface_unscaled = copy_surface
+
+
+# Wrapper around image loading.
+
+def load_image(f, filename):
+    surf = pygame.image.load(f, renpy.exports.fsencode(filename))
+    rv = copy_surface_unscaled(surf)
+    return rv
+
+load_image_unscaled = load_image
+
+
+# Wrapper around functions we use from pygame.surface.
+
+def flip(surf, horizontal, vertical):
+    surf = pygame.transform.flip(surf, horizontal, vertical)
+    return copy_surface_unscaled(surf)
+
+flip_unscaled = flip
+
+
+def rotozoom(surf, angle, zoom):
+
+    surf = pygame.transform.rotozoom(surf, angle, zoom)
+    return copy_surface_unscaled(surf)
+
+rotozoom_unscaled = rotozoom
+
+
+def transform_scale(surf, size):
+    surf = pygame.transform.scale(surf, size)
+    return copy_surface_unscaled(surf, surf)
+
+transform_scale_unscaled = transform_scale
+
+
+def transform_rotate(surf, angle):
+    surf = pygame.transform.rotate(surf, angle)
+    return copy_surface(surf)
+
+transform_rotate_unscaled = transform_rotate
+
+
+
diff --git a/unrpyc/renpy/display/predict.py b/unrpyc/renpy/display/predict.py
new file mode 100644
index 0000000..2bdc16f
--- /dev/null
+++ b/unrpyc/renpy/display/predict.py
@@ -0,0 +1,156 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This file contains the routines that manage image prediction.
+
+import renpy.display
+
+# Called to indicate an image should be loaded or preloaded. This is
+# a function that takes an image manipulator, set by reset and predict,
+# and winds up bound to either im.cache.get or im.cache.preload_image
+image = None
+
+# The set of displayables we've predicted since reset was last called.
+predicted = set()
+
+# A flag that indicates if we're currently predicting.
+predicting = False
+
+# A list of (screen name, argument dict) tuples, giving the screens we'd
+# like to predict.
+screens = [ ]
+
+def displayable(d):
+    """
+    Called to predict that the displayable `d` will be shown.
+    """
+
+    if d is None:
+        return
+
+    if d not in predicted:
+        predicted.add(d)
+        d.visit_all(lambda i : i.predict_one())
+
+def screen(_screen_name, *args, **kwargs):
+    """
+    Called to predict that the named screen is about to be shown
+    with the given arguments.
+    """
+
+    screens.append((_screen_name, args, kwargs))
+
+    
+def reset():
+    global image
+    image = renpy.display.im.cache.get
+    predicted.clear()
+    del screens[:]
+
+    
+def prediction_coroutine(root_widget):
+    """
+    The image prediction co-routine. This predicts the images that can
+    be loaded in the near future, and passes them to the image cache's
+    preload_image method to be queued up for loading.
+    
+    The .send should be called with True to do a expensive prediction,
+    and with False to either do an inexpensive prediction or no 
+    prediction at all.
+    
+    Returns True if there's more predicting to be done, or False
+    if there's no more predicting worth doing.
+    """
+
+    global predicting
+
+    # Wait to be told to start.
+    yield True
+
+    # Start the prediction thread (to clean out the cache).
+    renpy.display.im.cache.start_prediction()
+
+    # Set up the image prediction method.
+    global image
+    image = renpy.display.im.cache.preload_image
+
+    # Predict images that are going to be reached in the next few
+    # clicks.
+    predicting = True
+
+    for _i in renpy.game.context().predict():
+
+        predicting = False
+        yield True
+        predicting = True
+    
+    # If there's a parent context, predict we'll be returning to it
+    # shortly. Otherwise, call the functions in
+    # config.predict_callbacks.
+    
+    if len(renpy.game.contexts) >= 2:
+        sls = renpy.game.contexts[-2].scene_lists
+
+        for l in sls.layers.values():
+            for sle in l:                
+                try:
+                    displayable(sle.displayable)
+                except:
+                    pass
+
+    else:
+        for i in renpy.config.predict_callbacks:
+            i()
+                
+    predicting = False
+                
+    while not (yield True):
+        continue
+
+    # Predict things (especially screens) that are reachable through
+    # an action.
+    predicting = True
+
+    try:
+        root_widget.visit_all(lambda i : i.predict_one_action())
+    except:
+        pass
+
+    predicting = False
+
+    # Predict the screens themselves.
+    for name, args, kwargs in screens:
+        while not (yield True):
+            continue 
+
+        predicting = True
+        
+        try:
+            renpy.display.screen.predict_screen(name, *args, **kwargs)
+        except:
+            if renpy.config.debug_image_cache:
+                renpy.display.ic_log.write("While predicting screen %s %r", name, kwargs)
+                renpy.display.ic_log.exception()
+
+        predicting = False
+    
+    yield False
+                
diff --git a/unrpyc/renpy/display/presplash.py b/unrpyc/renpy/display/presplash.py
new file mode 100644
index 0000000..54717fd
--- /dev/null
+++ b/unrpyc/renpy/display/presplash.py
@@ -0,0 +1,113 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# Pre-splash code. The goal of this code is to try to get a pre-splash
+# screen up as soon as possible, to let the user know something is
+# going on.
+
+# The presplash process, if any.
+proc = None
+
+# Called from the main process. This determines if
+# we're even doing presplash, and if so what will be shown to the
+# user. If it decides to show something to the user, uses subprocess
+# to actually handle the showing.
+def start(basedir, gamedir):
+    import os.path
+
+    if "RENPY_LESS_UPDATES" in os.environ:
+        return
+    
+    global proc
+
+    filenames = [ "/presplash.png", "/presplash.jpg" ]
+    for fn in filenames:
+        fn = gamedir + fn
+        if os.path.exists(fn):
+            break
+    else:
+        return
+
+    try:    
+        import subprocess
+        import sys
+
+        cmd = [sys.executable, "-EOO", sys.argv[0], "show", "presplash", fn]
+            
+        def fsencode(s):
+            if isinstance(s, str):
+                return s
+            
+            return s.encode(sys.getfilesystemencoding() or "utf-8", "replace")
+            
+        proc = subprocess.Popen([ fsencode(i) for i in cmd ], stdin=subprocess.PIPE, stdout=subprocess.PIPE)
+    except:
+        pass
+            
+            
+# Called just before we initialize the display for real, to
+# hide the splash, and terminate window centering.
+def end():
+    
+    global proc
+
+    if not proc:
+        return
+
+    proc.stdin.close()
+    proc.wait()
+
+    proc = None
+    
+# Called in the presplash process, to actually display the presplash.
+def show(fn):
+
+    import pygame.display
+    import pygame.constants
+    import sys
+    import os
+    
+    os.environ['SDL_VIDEO_CENTERED'] = "1"
+        
+    try:
+        import pygame.macosx
+        pygame.macosx.init() #@UndefinedVariable
+    except:
+        pass
+
+    try:
+        import pygame.macosx #@Reimport
+        pygame.macosx.Video_AutoInit()
+    except:
+        pass
+
+    pygame.display.init()
+
+    img = pygame.image.load(fn, fn)
+    screen = pygame.display.set_mode(img.get_size(), pygame.constants.NOFRAME)
+    screen.blit(img, (0, 0))
+    pygame.display.update()
+
+    sys.stdout.write("READY\r\n")
+    sys.stdout.flush()
+    sys.stdin.read()
+    
+    sys.exit(0)
diff --git a/unrpyc/renpy/display/render.pxd b/unrpyc/renpy/display/render.pxd
new file mode 100644
index 0000000..a4d8d68
--- /dev/null
+++ b/unrpyc/renpy/display/render.pxd
@@ -0,0 +1,47 @@
+cdef class Matrix2D:
+    cdef public double xdx
+    cdef public double xdy
+    cdef public double ydx
+    cdef public double ydy
+
+    cpdef tuple transform(Matrix2D self, double x, double y)
+
+cdef class Render:
+
+    cdef public bint mark, cache_killed
+
+    cdef public float width, height
+    cdef public object layer_name
+
+    cdef public list children
+    cdef public set parents
+    cdef public list depends_on_list
+
+    cdef public int operation
+    cdef public double operation_complete
+    cdef public bint operation_alpha
+    cdef public object operation_parameter
+
+    cdef public Matrix2D forward, reverse
+    cdef public double alpha
+    
+    cdef public list focuses
+    cdef public list pass_focuses
+    cdef public object draw_func
+    cdef public object render_of
+    
+    cdef public bint opaque
+    cdef public list visible_children
+
+    cdef public bint clipping
+
+    cdef public object surface, alpha_surface, half_cache
+
+    cdef public bint modal
+
+    cpdef int blit(Render self, source, tuple pos, object focus=*, object main=*, object index=*)
+    cpdef int subpixel_blit(Render self, source, tuple pos, object focus=*, object main=*, object index=*)
+
+    
+cpdef render(object d, object widtho, object heighto, double st, double at)
+
diff --git a/unrpyc/renpy/display/render.pyx b/unrpyc/renpy/display/render.pyx
new file mode 100644
index 0000000..5aa45a0
--- /dev/null
+++ b/unrpyc/renpy/display/render.pyx
@@ -0,0 +1,1174 @@
+#cython: profile=False
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import collections
+import pygame
+import threading
+import renpy
+import gc
+
+# We grab the blit lock each time it is necessary to blit
+# something. This allows call to the pygame.transform functions to
+# disable blitting, should it prove necessary.
+blit_lock = threading.Condition()
+
+# This is a dictionary containing all the renders that we know of. It's a
+# map from displayable to dictionaries containing the render of that
+# displayable.
+render_cache = collections.defaultdict(dict)
+
+# The queue of redraws. A list of (time, displayable) pairs.
+redraw_queue = [ ]
+
+# The render returned from render_screen.
+screen_render = None
+
+# A list of renders the system knows about, and thinks are still alive.
+cdef list live_renders
+live_renders = [ ]
+
+# A copy of renpy.display.interface.frame_time, for speed reasons.
+cdef double frame_time
+frame_time = 0
+
+def free_memory():
+    """
+    Frees memory used by the render system.
+    """
+
+    global screen_render    
+    screen_render = None
+
+    mark_sweep()
+
+    render_cache.clear()
+
+    # This can hang onto a render.
+    renpy.display.interface.surftree = None
+    
+
+def check_at_shutdown():
+    """
+    This is called at shutdown time to check that everything went okay.
+    The big thing it checks for is memory leaks.
+    """
+
+    if not renpy.config.developer:
+        return
+
+    free_memory()
+
+    gc.collect()    
+    l = gc.get_objects()
+
+    count = 0
+    objects = gc.get_objects()
+    
+    for i in objects:
+        if isinstance(i, Render):
+            count += 1
+
+    if count:
+        raise Exception("%d Renders are alive at shutdown. This is probably a memory leak bug in Ren'Py." % count)
+
+    
+    
+cpdef render(d, object widtho, object heighto, double st, double at):
+    """
+    :doc: udd_utility
+    :args: (d, width, height, st, at)
+    
+    Causes a displayable to be rendered, and a renpy.Render object to
+    be returned.
+    
+    `d`
+        The displayable to render.
+        
+    `width`, `height`
+        The width and height available for the displayable to render into. 
+        
+    `st`, `at`
+        The shown and animation timebases.
+
+    Renders returned by this object may be cached, and should not be modified
+    once they have been retrieved.
+    """
+
+    cdef float width, height
+    cdef float orig_width, orig_height
+    cdef tuple orig_wh, wh
+    cdef dict render_cache_d
+    cdef Render rv
+    
+    orig_wh = (widtho, heighto, frame_time-st, frame_time-at)
+
+    render_cache_d = render_cache[d]
+    rv = render_cache_d.get(orig_wh, None)
+
+    if rv is not None:
+        return rv
+
+    orig_width = width = widtho
+    orig_height = height = heighto
+    
+    style = d.style
+    xmaximum = style.xmaximum
+    ymaximum = style.ymaximum
+    
+    if xmaximum is not None:
+        if isinstance(xmaximum, float):
+            width = width * xmaximum
+        else:
+            width = min(xmaximum, width)
+
+    if ymaximum is not None:
+        if isinstance(ymaximum, float):
+            height = height * ymaximum
+        else:
+            height = min(ymaximum, height)
+
+    if width < 0:
+        width = 0
+    if height < 0:
+        height = 0
+
+    if orig_width != width or orig_height != height:        
+        widtho = width
+        heighto = height
+        wh = (widtho, heighto, frame_time-st, frame_time-at)
+        rv = render_cache_d.get(wh, None)
+
+        if rv is not None:
+            return rv
+
+    else:
+        wh = orig_wh
+            
+    rv = d.render(widtho, heighto, st, at)
+
+    rv.render_of.append(d)
+
+    if style.clipping:
+        rv = rv.subsurface((0, 0, rv.width, rv.height), focus=True)
+        rv.render_of.append(d)
+
+    render_cache_d[wh] = rv
+
+    if wh is not orig_wh:
+        render_cache_d[orig_wh] = rv
+
+    return rv
+
+
+# This is true if something has been invalidated, and a redraw needs
+# to occur. It's automatically cleared to False at the end of each
+# redraw.
+invalidated = False
+
+def invalidate(d):
+    """
+    Removes d from the render cache. If we're not in a redraw, triggers
+    a redraw to start.
+    """
+
+    global invalidated
+    
+    if d in render_cache:
+        for v in render_cache[d].values():
+            v.kill_cache()
+
+        invalidated = True
+
+        
+def process_redraws():
+    """
+    Called to determine if any redraws are pending. Returns true if we
+    need to redraw the screen now, false otherwise.
+    """
+
+    global redraw_queue
+    
+    redraw_queue.sort()
+
+    now = renpy.display.core.get_time()
+    rv = invalidated
+
+    new_redraw_queue = [ ]
+    seen = set()
+
+    for t in redraw_queue:
+        when, d = t
+
+        if d in seen:
+            continue
+
+        seen.add(d)
+
+        if d not in render_cache:
+            continue
+        
+        if when <= now:
+            # Remove this displayable and all its parents from the
+            # render cache. But don't kill them yet, as that will kill the
+            # children that we want to reuse.
+
+            for v in render_cache[d].values():
+                v.kill_cache()
+
+            rv = True
+
+        else:
+            new_redraw_queue.append(t)
+
+    redraw_queue = new_redraw_queue
+            
+    return rv
+
+
+def redraw_time():
+    """
+    Returns the time at which the next redraw is scheduled.
+    """
+
+    if redraw_queue:
+        return redraw_queue[0][0]
+
+    return None
+    
+
+def redraw(d, when):
+    """
+    :doc: udd_utility
+    
+    Causes the displayable `d` to be redrawn after `when` seconds have
+    elapsed.
+    """
+
+    if not renpy.game.interface:
+        return
+
+    redraw_queue.append((when + renpy.game.interface.frame_time, d))
+    
+
+cdef class Matrix2D:
+    """
+    This represents a 2d matrix that can be used to transform
+    points and things like that.
+    """
+
+    def __getstate__(self):
+        return dict(
+            xdx = self.xdx,
+            xdy = self.xdy,
+            ydx = self.ydx,
+            ydy = self.ydy)
+
+    def __setstate__(self, state):
+        self.xdx = state['xdx']
+        self.xdy = state['xdy']
+        self.ydx = state['ydx']
+        self.ydy = state['ydy']
+        
+    def __init__(Matrix2D self, double xdx, double xdy, double ydx, double ydy):
+        self.xdx = xdx
+        self.xdy = xdy
+        self.ydx = ydx
+        self.ydy = ydy
+
+    cpdef tuple transform(Matrix2D self, double x, double y):
+        return (x * self.xdx + y * self.xdy), (x * self.ydx + y * self.ydy)
+
+    def __mul__(Matrix2D self, Matrix2D other):
+        return Matrix2D(
+            other.xdx * self.xdx + other.xdy * self.ydx,
+            other.xdx * self.xdy + other.xdy * self.ydy,
+            other.ydx * self.xdx + other.ydy * self.ydx,
+            other.ydx * self.xdy + other.ydy * self.ydy)
+
+    def __repr__(self):
+        return "Matrix2D(xdx=%f, xdy=%f, ydx=%f, ydy=%f)" % (self.xdx, self.xdy, self.ydx, self.ydy)
+    
+IDENTITY = Matrix2D(1, 0, 0, 1)
+
+def take_focuses(focuses):
+    """
+    Adds a list of rectangular focus regions to the focuses list.
+    """
+
+    screen_render.take_focuses(
+        0, 0, screen_render.width, screen_render.height,
+        IDENTITY, 0, 0, focuses)
+
+# The result of focus_at_point for a modal render. This overrides any
+# specific focus from below us.
+Modal = object()
+    
+def focus_at_point(x, y):
+    """
+    Returns a focus object corresponding to the uppermost displayable
+    at point, or None if nothing focusable is at point.
+    """
+
+    if screen_render is None:
+        return None
+    
+    cf = screen_render.focus_at_point(x, y)
+    if cf is None or cf is Modal:
+        return None
+    else:
+        d, arg = cf
+        return renpy.display.focus.Focus(d, arg, None, None, None, None)
+
+    
+def mutated_surface(surf):
+    """
+    Called to indicate that the given surface has changed. 
+    """
+
+    renpy.display.draw.mutated_surface(surf)
+
+
+def render_screen(root, width, height):
+    """
+    Renders `root` (a displayable) as the root of a screen with the given
+    `width` and `height`.
+    """
+
+
+    
+    global old_screen_render
+    global screen_render
+    global invalidated
+    global frame_time
+
+    frame_time = renpy.display.interface.frame_time
+    
+    rv = render(root, width, height, 0, 0)
+    screen_render = rv
+    
+    invalidated = False
+
+    rv.is_opaque()
+    
+    return rv
+
+def mark_sweep():
+    """
+    This performs mark-and-sweep garbage collection on the live_renders
+    list.
+    """
+
+    global live_renders
+
+    cdef list worklist
+    cdef int i
+    cdef Render r, j
+    
+    worklist = [ ]
+
+    if screen_render is not None:
+        worklist.append(screen_render)
+
+    i = 0
+
+    while i < len(worklist):
+        r = worklist[i]
+
+        for j in r.depends_on_list:
+            if not j.mark:
+                j.mark = True
+                worklist.append(j)
+
+        i += 1
+
+    for r in live_renders:
+        if not r.mark:
+            r.kill_cache()
+        else:
+            r.mark = False
+
+    live_renders = worklist
+
+def compute_subline(sx0, sw, cx0, cw):
+    """
+    Given a source line (start sx0, width sw) and a crop line (cx0, cw),
+    return three things:
+
+    * The offset of the portion of the source line that overlaps with
+      the crop line, relative to the crop line.
+    * The offset of the portion of the source line that overlaps with the
+      the crop line, relative to the source line.
+    * The length of the overlap in pixels. (can be <= 0) 
+    """
+
+    sx1 = sx0 + sw
+    cx1 = cx0 + cw
+
+    if sx0 > cx0:
+        start = sx0
+    else:
+        start = cx0
+
+    offset = start - cx0
+    crop = start - sx0
+
+    if sx1 < cx1:
+        width = sx1 - start
+    else:
+        width = cx1 - start
+
+        
+    return offset, crop, width
+
+    
+
+
+# Possible operations that can be done as part of a render.
+BLIT = 0
+DISSOLVE = 1
+IMAGEDISSOLVE = 2
+PIXELLATE = 3
+
+cdef class Render:
+
+    def __init__(Render self, float width, float height, draw_func=None, layer_name=None, bint opaque=None): #@DuplicatedSignature
+        """
+        Creates a new render corresponding to the given widget with
+        the specified width and height.
+
+        If `layer_name` is given, then this render corresponds to a
+        layer.
+        """
+
+        # The mark bit, used for mark/sweep-style garbage collection of
+        # renders.
+        self.mark = False
+
+        # Is has this render been removed from the cache?
+        self.cache_killed = False
+        
+        
+        self.width = width
+        self.height = height
+
+        self.layer_name = layer_name
+
+        # A list of (surface/render, xoffset, yoffset, focus, main) tuples, ordered from
+        # back to front.
+        self.children = [ ]
+
+        # The set of renders that either have us as children, or depend on
+        # us.
+        self.parents = set()
+
+        # The renders we depend on, including our children.
+        self.depends_on_list = [ ]
+        
+        # The operation we're performing. (BLIT, DISSOLVE, OR IMAGE_DISSOLVE)
+        self.operation = BLIT
+
+        # The fraction of the operation that is complete.
+        self.operation_complete = 0.0
+
+        # Should the dissolve operations preserve alpha?
+        self.operation_alpha = False
+        
+        # The parameter to the operation. 
+        self.operation_parameter = 0
+        
+        # Forward is used to transform from screen coordinates to child
+        # coordinates.
+        # Reverse is used to transform from child coordinates to screen
+        # coordinates.
+        # 
+        # For performance reasons, these aren't used to transform the
+        # x and y offsets found in self.children. Those offsets should
+        # be of the (0, 0) point in the child coordinate space.
+        self.forward = None
+        self.reverse = None
+
+        # This is used to adjust the alpha of children of this render.
+        self.alpha = 1
+        
+        # A list of focus regions in this displayable.
+        self.focuses = None
+
+        # Other renders that we should pass focus onto.
+        self.pass_focuses = None
+        
+        # The displayable(s) that this is a render of. (Set by render)
+        self.render_of = [ ]
+
+        # If set, this is a function that's called to draw this render
+        # instead of the default.
+        self.draw_func = draw_func
+
+        # Is this displayable opaque? (May be set on init, or later on
+        # if we have opaque children.) This may be True, False, or None
+        # to indicate we don't know yet.
+        self.opaque = opaque
+
+        # A list of our visible children. (That is, children above and
+        # including our uppermost opaque child.) If nothing is opaque,
+        # includes all children.
+        self.visible_children = self.children
+        
+        # Should children be clipped to a rectangle?
+        self.clipping = False
+
+        # Caches of the texture created by rendering this surface.
+        self.surface = None
+        self.alpha_surface = None
+
+        # Cache of the texture created by rendering this surface at half size.
+        # (This is set in gldraw.)
+        self.half_cache = None
+
+        # Are we modal?
+        self.modal = False
+        
+        live_renders.append(self)
+        
+    def __repr__(self): #@DuplicatedSignature
+        return "<Render %x of %r>" % (id(self), self.render_of)
+
+    def __getstate__(self): #@DuplicatedSignature
+        if renpy.config.developer:
+            raise Exception("Can't pickle a Render.")
+        else:
+            return { }
+        
+    def __setstate__(self, state): #@DuplicatedSignature
+        return
+
+    cpdef int blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None):
+        """
+        Blits `source` (a Render or Surface) to this Render, offset by
+        xo and yo.
+
+        If `focus` is true, then focuses are added from the child to the
+        parent.
+
+        This will only blit on integer pixel boundaries.
+        """
+
+        (xo, yo) = pos
+        
+        if source is self:
+            raise Exception("Blitting to self.")
+        
+        xo = int(xo)
+        yo = int(yo)
+
+        if index is None:
+            self.children.append((source, xo, yo, focus, main))
+        else:
+            self.children.insert(index, (source, xo, yo, focus, main))
+                    
+        if isinstance(source, Render):
+            self.depends_on_list.append(source)
+            source.parents.add(self)
+
+        return 0
+        
+    cpdef int subpixel_blit(Render self, source, tuple pos, object focus=True, object main=True, object index=None):
+        """
+        Blits `source` (a Render or Surface) to this Render, offset by
+        xo and yo.
+
+        If `focus` is true, then focuses are added from the child to the
+        parent.
+
+        This blits at fractional pixel boundaries.
+        """
+
+        (xo, yo) = pos
+        
+        xo = float(xo)
+        yo = float(yo)
+        
+        if index is None:
+            self.children.append((source, xo, yo, focus, main))
+        else:
+            self.children.insert(index, (source, xo, yo, focus, main))
+                        
+        if isinstance(source, Render):
+            self.depends_on_list.append(source)
+            source.parents.add(self)
+
+        return 0
+            
+    def get_size(self):
+        """
+        Returns the size of this Render, a mostly ficticious value
+        that's taken from the inputs to the constructor. (As in, we
+        don't clip to this size.)
+        """
+
+        return self.width, self.height
+
+    
+    def render_to_texture(self, alpha=True):
+        """
+        Returns a texture constructed from this render. This may return
+        a cached textue, if one has already been rendered.
+
+        `alpha` is a hint that controls if the surface should have
+        alpha or not.
+        """        
+
+        if alpha:
+            if self.alpha_surface is not None:
+                return self.alpha_surface
+        else:
+            if self.surface is not None:
+                return self.surface
+            
+        rv = None
+
+        opaque = self.is_opaque()
+
+        # If we can, reuse a child's texture.
+        if opaque or alpha:
+
+            if not self.forward and len(self.children) == 1:
+                child, x, y, focus, main = self.children[0]
+                cw, ch = child.get_size()
+                if x <= 0 and y <= 0 and cw + x >= self.width and ch + y >= self.height:
+                    # Our single child overlaps us.
+                    if isinstance(child, Render):
+                        child = child.render_to_texture(alpha)
+
+                    if x != 0 or y != 0 or cw != self.width or ch != self.height:
+                        rv = child.subsurface((-x, -y, self.width, self.height))
+                    else:
+                        rv = child
+
+        # Otherwise, render to a texture.
+        if rv is None:
+            # is_opaque has already been called.
+            rv = renpy.display.draw.render_to_texture(self, alpha)
+            
+        # Stash and return the surface.
+        if alpha:
+            self.alpha_surface = rv
+        else:
+            self.surface = rv
+            
+        return rv
+    
+    pygame_surface = render_to_texture
+    
+    def subsurface(self, rect, focus=False):
+        """
+        Returns a subsurface of this render. If `focus` is true, then
+        the focuses are copied from this render to the child.
+        """
+
+        (x, y, w, h) = rect
+        rv = Render(w, h)
+
+        reverse = self.reverse
+        
+        # This doesn't actually make a subsurface, as we can't easily do
+        # so for non-rectangle-aligned renders.
+        if (reverse is not None) and (
+            reverse.xdx != 1.0 or
+            reverse.xdy != 0.0 or
+            reverse.ydx != 0.0 or
+            reverse.ydy != 1.0):
+
+            rv.clipping = True
+            rv.blit(self, (-x, -y), focus=focus, main=True)            
+            return rv
+
+        # This is the path that executes for rectangle-aligned surfaces,
+        # making an actual subsurface.
+
+        for child, cx, cy, cfocus, cmain in self.children:
+
+            cw, ch = child.get_size()            
+            xo, cx, cw = compute_subline(cx, cw, x, w)
+            yo, cy, ch = compute_subline(cy, ch, y, h)
+            
+            if cw <= 0 or ch <= 0:
+                continue
+
+            crop = (cx, cy, cw, ch)
+            offset = (xo, yo)
+
+            if isinstance(child, Render):
+                newchild = child.subsurface(crop, focus=focus)
+                newchild.render_of = child.render_of[:]
+            else:
+                newchild = child.subsurface(crop)
+                renpy.display.draw.mutated_surface(newchild)
+                
+            rv.blit(newchild, offset, focus=cfocus, main=cmain)
+
+        if focus and self.focuses:
+
+            for (d, arg, xo, yo, fw, fh, mx, my, mask) in self.focuses:
+
+                if xo is None:
+                    rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask)
+                    continue
+
+                xo, cx, fw = compute_subline(xo, fw, x, w)
+                yo, cy, fh = compute_subline(yo, fh, y, h)
+
+                if cw <= 0 or ch <= 0:
+                    continue
+                
+                if mx is not None:
+
+                    mw, mh = mask.get_size()
+
+                    mx, mcx, mw = compute_subline(mx, mw, x, w) 
+                    my, mcy, mh = compute_subline(my, mh, y, h)
+                    
+                    if mw <= 0 or mh <= 0:
+                        mx = None
+                        my = None
+                        mask = None
+                    else:
+                        mask = mask.subsurface((mcx, mcy, mw, mh))
+
+                rv.add_focus(d, arg, xo, yo, fw, fh, mx, my, mask)
+
+        rv.depends_on(self)
+        rv.alpha = self.alpha
+        rv.operation = self.operation
+        rv.operation_alpha = self.operation_alpha
+        rv.operation_complete = self.operation_complete
+        
+        return rv
+    
+        
+    def depends_on(self, source, focus=False):
+        """
+        Used to indicate that this render depends on another
+        render. Useful, for example, if we use pygame_surface to make
+        a surface, and then blit that surface into another render.
+        """
+
+        if source is self:
+            raise Exception("Render depends on itself.")
+
+        self.depends_on_list.append(source)
+        source.parents.add(self)
+        
+        if focus:
+            if self.pass_focuses is None:
+                self.pass_focuses = [ source ]
+            else:            
+                self.pass_focuses.append(source)
+
+        
+    def kill_cache(self):
+        """
+        Removes this render and its transitive parents from the cache.
+        """
+
+        if self.cache_killed:
+            return
+
+        self.cache_killed = True
+
+        for i in self.parents:
+            i.kill_cache()
+
+        self.parents.clear()
+                
+        for i in self.depends_on_list:
+            if not i.cache_killed:
+                i.parents.discard(self)
+
+        for ro in self.render_of:
+            cache = render_cache[ro]
+            for k, v in cache.items():
+                if v is self:
+                    del cache[k]
+                    
+            if not cache:
+                del render_cache[ro]
+
+    def kill(self):
+        """
+        Retained for compatibility.
+        """
+                
+    def add_focus(self, d, arg=None, x=0, y=0, w=None, h=None, mx=None, my=None, mask=None):
+        """
+        This is called to indicate a region of the screen that can be
+        focused.
+
+        `d` - the displayable that is being focused.
+        `arg` - an argument.
+
+        The rest of the parameters are a rectangle giving the portion of
+        this region corresponding to the focus. If they are all None, than
+        this focus is assumed to be the singular full-screen focus.
+        """
+
+        if mask is not None and mask is not self:
+            self.depends_on(mask)
+
+        t = (d, arg, x, y, w, h, mx, my, mask)
+            
+        if self.focuses is None:
+            self.focuses = [ t ]
+        else:
+            self.focuses.append(t)
+
+    def take_focuses(self, cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses): #@DuplicatedSignature
+        """
+        This adds to focuses Focus objects corresponding to the focuses
+        added to this object and its children, transformed into screen
+        coordinates.
+
+        `cminx`, `cminy`, `cmaxx`, `cmaxy` - The clipping rectangle.
+        `reverse` - The transform from render to screen coordinates.
+        `x`, `y` - The offset of the upper-left corner of the render.
+        `focuses` - The list of focuses to add to.
+        """
+        
+        if self.modal:
+            focuses[:] = [ ]
+            
+        if self.reverse:
+            reverse = reverse * self.reverse
+
+        if self.focuses:
+
+            for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses:
+
+                if xo is None:
+                    focuses.append(renpy.display.focus.Focus(d, arg, None, None, None, None)) 
+                    continue
+
+                x1, y1 = reverse.transform(xo, yo)
+                x2, y2 = reverse.transform(xo + w, yo + h)
+
+                minx = min(x1, x2) + x
+                miny = min(y1, y2) + y
+                maxx = max(x1, x2) + x
+                maxy = max(y1, y2) + y
+
+                minx = max(minx, cminx)
+                miny = max(miny, cminy)
+                maxx = min(maxx, cmaxx)
+                maxy = min(maxy, cmaxy)
+
+                if minx >= maxx or miny >= maxy:
+                    continue
+
+                focuses.append(renpy.display.focus.Focus(d, arg, minx, miny, maxx - minx, maxy - miny)) 
+
+        if self.clipping:
+            cminx = max(cminx, x)
+            cminy = max(cminy, y)
+            cmaxx = min(cmaxx, x + self.width)
+            cmaxy = min(cmaxx, x + self.height)
+
+        for child, xo, yo, focus, main in self.children:
+            if not focus or not isinstance(child, Render):
+                continue
+
+            xo, yo = reverse.transform(xo, yo)
+            child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x + xo, y + yo, focuses)
+
+        if self.pass_focuses:
+            for child in self.pass_focuses:
+                child.take_focuses(cminx, cminy, cmaxx, cmaxy, reverse, x, y, focuses)
+        
+    def focus_at_point(self, x, y): #@DuplicatedSignature
+        """
+        This returns the focus of this object at the given point.
+        """
+
+        if self.clipping:
+            if x < 0 or x >= self.width or y < 0 or y >= self.height:
+                return None
+        
+        rv = None
+
+        if self.focuses:
+            for (d, arg, xo, yo, w, h, mx, my, mask) in self.focuses:
+
+                if xo is None:
+                    continue
+
+                elif mx is not None:
+                    cx = x - mx
+                    cy = y - my
+
+                    if self.forward:
+                        cx, cy = self.forward.transform(cx, cy)
+
+                    if mask.is_pixel_opaque(cx, cy):
+                        rv = d, arg
+
+                elif xo <= x < xo + w and yo <= y < yo + h:
+                    rv = d, arg
+            
+        for child, xo, yo, focus, main in self.children:
+
+            if not focus or not isinstance(child, Render):
+                continue
+            
+            cx = x - xo
+            cy = y - yo
+
+            if self.forward:
+                cx, cy = self.forward.transform(cx, cy)
+
+            cf = child.focus_at_point(cx, cy)
+            if cf is not None:
+                rv = cf
+
+        if self.pass_focuses:
+            for child in self.pass_focuses:
+                cf = child.focus_at_point(x, y)
+                if cf is not None:
+                    rv = cf
+
+        if rv is None and self.modal:
+            rv = Modal
+                
+        return rv
+        
+            
+    def main_displayables_at_point(self, x, y, layers, depth=None):
+        """
+        Returns the displayable at `x`, `y` on one of the layers in
+        the set or list `layers`.
+        """
+
+        rv = [ ]
+
+        if x < 0 or y < 0 or x >= self.width or y >= self.height:
+            return rv
+
+        if depth is not None:
+            for d in self.render_of:
+                rv.append((depth, self.width, self.height, d))
+                depth += 1
+        elif self.layer_name in layers:
+            depth = 0
+
+        for (child, xo, yo, focus, main) in self.children:
+            if not main or not isinstance(child, Render):
+                continue
+
+            cx = x - xo
+            cy = y - yo
+
+            if self.forward:
+                cx, cy = self.forward.transform(cx, cy)
+
+            cf = child.main_displayables_at_point(cx, cy, layers, depth)
+            rv.extend(cf)            
+
+        return rv
+        
+
+    def is_opaque(self):
+        """
+        Returns true if this displayable is opaque, or False otherwise.
+        Also sets self.visible_children.
+        """
+
+        if self.opaque is not None:
+            return self.opaque
+
+        # A rotated image is never opaque. (This isn't actually true, but it
+        # saves us from the expensive calculations require to prove it is.)
+        if self.forward:
+            self.opaque = False
+            return False
+        
+        rv = False
+        vc = [ ]
+        
+        for i in self.children:
+            child, xo, yo, focus, main = i
+
+            if xo <= 0 and yo <= 0:
+                cw, ch = child.get_size()
+                if cw + xo < self.width or ch + yo < self.height:
+                    if child.is_opaque():
+                        vc = [ ]
+                        rv = True
+              
+            vc.append(i)
+
+        self.visible_children = vc
+        self.opaque = rv
+        return rv
+
+    
+    def is_pixel_opaque(self, x, y):
+        """
+        Determine if the pixel at x and y is opaque or not.
+        """
+
+        if x < 0 or y < 0 or x >= self.width or y >= self.height:
+            return False
+
+        if self.is_opaque():
+            return True
+
+        return renpy.display.draw.is_pixel_opaque(self, x, y)
+
+    
+    def fill(self, color):
+        """
+        Fills this Render with the given color.
+        """
+
+        color = renpy.easy.color(color)
+        solid = renpy.display.imagelike.Solid(color)
+        surf = render(solid, self.width, self.height, 0, 0)
+        self.blit(surf, (0, 0), focus=False, main=False)
+
+        
+    def canvas(self):
+        """
+        Returns a canvas object that draws to this Render.
+        """
+
+        surf = renpy.display.pgrender.surface((self.width, self.height), True)
+
+        mutated_surface(surf)
+
+        self.blit(surf, (0, 0))
+
+        return Canvas(surf)
+
+        
+class Canvas(object):
+
+    def __init__(self, surf): #@DuplicatedSignature
+        self.surf = surf
+        
+    def rect(self, color, rect, width=0):
+
+        try:
+            blit_lock.acquire()
+            pygame.draw.rect(self.surf,
+                             renpy.easy.color(color),
+                             rect,
+                             width)
+        finally:
+            blit_lock.release()
+
+    def polygon(self, color, pointlist, width=0):
+        try:
+            blit_lock.acquire()
+            pygame.draw.polygon(self.surf,
+                                renpy.easy.color(color),
+                                pointlist,
+                                width)
+        finally:
+            blit_lock.release()
+
+    def circle(self, color, pos, radius, width=0):
+
+        try:
+            blit_lock.acquire()
+            pygame.draw.circle(self.surf,
+                               renpy.easy.color(color),
+                               pos,
+                               radius,
+                               width)
+
+        finally:
+            blit_lock.release()
+
+    def ellipse(self, color, rect, width=0):
+        try:
+            blit_lock.acquire()
+            pygame.draw.ellipse(self.surf,
+                                renpy.easy.color(color),
+                                rect,
+                                width)
+        finally:
+            blit_lock.release()
+
+
+    def arc(self, color, rect, start_angle, stop_angle, width=1):
+        try:
+            blit_lock.acquire()
+            pygame.draw.arc(self.surf,
+                            renpy.easy.color(color),
+                            rect,
+                            start_angle,
+                            stop_angle,
+                            width)
+        finally:
+            blit_lock.release()
+
+
+    def line(self, color, start_pos, end_pos, width=1):
+        try:
+            blit_lock.acquire()
+            pygame.draw.line(self.surf,
+                             renpy.easy.color(color),
+                             start_pos,
+                             end_pos,
+                             width)
+        finally:
+            blit_lock.release()
+
+    def lines(self, color, closed, pointlist, width=1):
+        try:
+            blit_lock.acquire()
+            pygame.draw.lines(self.surf,
+                              renpy.easy.color(color),
+                              closed,
+                              pointlist,
+                              width)
+        finally:
+            blit_lock.release()
+    
+    def aaline(self, color, startpos, endpos, blend=1):
+        try:
+            blit_lock.acquire()
+            pygame.draw.aaline(self.surf,
+                               renpy.easy.color(color),
+                               startpos,
+                               endpos,
+                               blend)
+        finally:
+            blit_lock.release()
+
+    def aalines(self, color, closed, pointlist, blend=1):
+        try:
+            blit_lock.acquire()
+            pygame.draw.aalines(self.surf,
+                                renpy.easy.color(color),
+                                closed,
+                                pointlist,
+                                blend)
+        finally:
+            blit_lock.release()
diff --git a/unrpyc/renpy/display/scale.py b/unrpyc/renpy/display/scale.py
new file mode 100644
index 0000000..c9efbcc
--- /dev/null
+++ b/unrpyc/renpy/display/scale.py
@@ -0,0 +1,109 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This used to hack pygame to support resolution-scaling. Now it just kinda
+# sits here, to provide compatibility with what it used to be.
+
+import pygame
+import renpy.display
+import renpy.display.pgrender as pgrender
+
+import _renpy
+
+##############################################################################
+# The scaling API that's used if we don't enable scaling.
+    
+# Gets the real pygame surface.
+def real(s):
+    return s
+
+# Scales the number, n.
+def scale(n):
+    return n
+
+def real_bilinear(src, size):
+    rv = pgrender.surface_unscaled(size, src)
+    renpy.display.module.bilinear_scale(src, rv)
+    return rv
+
+# Does pygame.transform.scale.
+def real_transform_scale(surf, size):
+    return pgrender.transform_scale_unscaled(surf, size)
+
+# Loads an image, without scaling it.
+def image_load_unscaled(f, hint, convert=True):
+    rv = pgrender.load_image_unscaled(f, hint)
+    return rv
+
+# Saves an image without rescaling.
+def image_save_unscaled(surf, filename):
+    pygame.image.save(surf, renpy.exports.fsencode(filename))
+
+# Scales down a surface.
+def surface_scale(full):
+    return full
+
+real_renpy_pixellate = _renpy.pixellate
+real_renpy_transform = _renpy.transform
+
+def real_smoothscale(src, size, dest=None):
+    """
+    This scales src up or down to size. This uses both the pixellate
+    and the transform operations to handle the scaling.
+    """
+
+    width, height = size
+    srcwidth, srcheight = src.get_size()
+    iwidth, iheight = srcwidth, srcheight
+
+    if dest is None:
+        dest = pgrender.surface_unscaled(size, src)
+
+    if width == 0 or height == 0:
+        return dest
+
+    xshrink = 1
+    yshrink = 1
+
+    while iwidth >= width * 2:
+        xshrink *= 2
+        iwidth /= 2
+
+    while iheight >= height * 2:
+        yshrink *= 2
+        iheight /= 2
+
+    if iwidth != srcwidth or iheight != srcheight:
+        inter = pgrender.surface_unscaled((iwidth, iheight), src)
+        real_renpy_pixellate(src, inter, xshrink, yshrink, 1, 1)
+        src = inter
+
+    real_renpy_transform(src, dest,
+                         0, 0,
+                         1.0 * iwidth / width , 0,                             
+                         0, 1.0 * iheight / height,
+                         precise=1,
+                         )
+
+    return dest
+    
+smoothscale = real_smoothscale
+    
diff --git a/unrpyc/renpy/display/screen.py b/unrpyc/renpy/display/screen.py
new file mode 100644
index 0000000..8fd529b
--- /dev/null
+++ b/unrpyc/renpy/display/screen.py
@@ -0,0 +1,639 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy.display
+
+class Screen(renpy.object.Object):
+    """
+    A screen is a collection of widgets that are displayed together.
+    This class stores information about the screen.
+    """
+
+    def __init__(self,
+                 name,
+                 function,
+                 modal="False",
+                 zorder="0",
+                 tag=None,
+                 predict=None,
+                 variant=None,
+                 parameters=False):
+    
+        # The name of this screen.
+        if isinstance(name, str):
+            name = tuple(name.split())
+
+        self.name = name
+        
+        screens[name[0], variant] = self
+        
+        # The function that is called to display this screen.
+        self.function = function
+
+        # Expression: Are we modal? (A modal screen ignores screens under it.)
+        self.modal = modal
+
+        # Expression: Our zorder.
+        self.zorder = zorder
+
+        # The tag associated with the screen.
+        self.tag = tag or name[0]
+
+        # Can this screen be predicted?
+        if predict is None:
+            predict = renpy.config.predict_screens
+
+        self.predict = predict
+        
+        # True if this screen takes parameters via _args and _kwargs.
+        self.parameters = parameters
+
+
+class ScreenDisplayable(renpy.display.layout.Container):
+    """
+    A screen is a collection of widgets that are displayed together. This
+    class is responsible for managing the display of a screen.
+    """
+
+    nosave = [ 'screen', 'child', 'transforms', 'widgets', 'old_widgets', 'old_transforms' ]
+
+    restarting = False
+
+    def after_setstate(self):
+        self.screen = get_screen_variant(self.screen_name[0])
+        self.child = None
+        self.transforms = { }
+        self.widgets = { }
+        self.old_widgets = None
+        self.old_transforms = None
+        
+    def __init__(self, screen, tag, layer, widget_properties={}, scope={}, **properties):
+
+        super(ScreenDisplayable, self).__init__(**properties)
+
+        # Stash the properties, so we can re-create the screen.
+        self.properties = properties
+        
+        # The screen, and it's name. (The name is used to look up the
+        # screen on save.)
+        self.screen = screen
+        self.screen_name = screen.name
+
+        # The tag and layer screen was displayed with.
+        self.tag = tag
+        self.layer = layer
+        
+        # The scope associated with this statement. This is passed in
+        # as keyword arguments to the displayable.
+        self.scope = renpy.python.RevertableDict(scope)
+        
+        # The child associated with this screen.
+        self.child = None
+
+        # Widget properties given to this screen the last time it was
+        # shown.
+        self.widget_properties = widget_properties
+
+        # A map from name to the widget with that name. 
+        self.widgets = { }
+
+        if tag and layer:
+            old_screen = get_screen(tag, layer)
+        else:
+            old_screen = None
+            
+        # A map from name to the transform with that name. (This is
+        # taken from the old version of the screen, if it exists.
+        if old_screen is not None:
+            self.transforms = old_screen.transforms
+        else:            
+            self.transforms = { }
+
+        # What widgets and transforms were the last time this screen was
+        # updated. Used to communicate with the ui module, and only
+        # valid during an update - not used at other times.
+        self.old_widgets = None
+        self.old_transforms = None
+
+        # Should we transfer data from the old_screen? This becomes
+        # true once this screen finishes updating for the first time,
+        # and also while we're using something.
+        self.old_transfers = (old_screen and old_screen.screen_name == self.screen_name)
+
+        # The current transform event, and the last transform event to
+        # be processed.
+        self.current_transform_event = None
+
+        # A dict-set of widgets (by id) that have been hidden from us.
+        self.hidden_widgets = { }
+        
+        # Are we hiding?
+        self.hiding = False
+
+        # Are we restarting?
+        self.restarting = False
+
+        # Modal and zorder.
+        self.modal = renpy.python.py_eval(self.screen.modal, locals=self.scope)
+        self.zorder = renpy.python.py_eval(self.screen.zorder, locals=self.scope)
+        
+    def __repr__(self):
+        return "<ScreenDisplayable: %r>" % (self.screen_name,)
+        
+    def visit(self):
+        return [ self.child ]
+
+    def per_interact(self):
+        renpy.display.render.redraw(self, 0)
+        self.update()
+
+    def set_transform_event(self, event):
+        super(ScreenDisplayable, self).set_transform_event(event)        
+        self.current_transform_event = event
+
+    def find_focusable(self, callback, focus_name):
+        if self.child and not self.hiding:
+            self.child.find_focusable(callback, focus_name)
+        
+    def _hide(self, st, at, kind):        
+
+        if self.hiding:
+            hid = self
+        else:
+            hid = ScreenDisplayable(self.screen, self.tag, self.layer, self.widget_properties, self.scope, **self.properties)
+            hid.transforms = self.transforms.copy()
+            hid.widgets = self.widgets.copy()
+            hid.old_transfers = True
+            
+        hid.hiding = True
+
+        hid.current_transform_event = kind
+        hid.update()
+
+        renpy.display.render.redraw(hid, 0)
+                
+        rv = None
+
+        # Compute the reverse of transforms and widgets.
+        reverse_transforms = dict((id(v), k) for k, v in hid.transforms.items())
+        reverse_widgets = dict((id(v), k) for k, v in hid.widgets.items())
+        
+        # Assumption: the only displayables that can keep us around
+        # are Transforms that handle hide.
+
+        # Iterate over our immediate children, trying to hide them.
+        for d in list(hid.child.children):
+
+            id_d = id(d)
+
+            # If we have a transform, call its _hide method. If that comes
+            # back non-None, store the new transform, and keep us alive.
+            #
+            # Otherwise, remove the child.            
+            name = reverse_transforms.get(id_d, None)
+
+            if name is not None:
+                c = d._hide(st, at, kind)
+
+                if c is not None:
+                    hid.transforms[name] = c
+                    rv = hid
+                else:
+                    hid.hidden_widgets[name] = True
+                    hid.child.remove(d)
+                    
+                continue
+
+            # Remove any non-transform children.
+            name = reverse_widgets.get(id_d, None)
+
+            if name is not None:
+                hid.hidden_widgets[name] = True
+                hid.child.remove(d)
+                
+        return rv
+    
+    def update(self):
+
+        # If we're restarting, do not update - the update can use variables 
+        # that are no longer in scope.
+        if self.restarting:
+            if not self.child:
+                self.child = renpy.display.layout.Null()
+            
+            return self.widgets
+
+        # Update _current_screen
+        global _current_screen
+        old_screen = _current_screen
+        _current_screen = self
+
+        # Cycle widgets and transforms.
+        self.old_widgets = self.widgets
+        self.old_transforms = self.transforms
+        self.widgets = { }
+        self.transforms = { }
+        
+        # Render the child.
+        old_ui_screen = renpy.ui.screen
+        renpy.ui.screen = self
+        
+        renpy.ui.detached()
+        self.child = renpy.ui.fixed(focus="_screen_" + "_".join(self.screen_name))
+        self.children = [ self.child ]
+        
+        self.scope["_scope"] = self.scope
+        self.scope["_name"] = 0
+         
+        self.screen.function(**self.scope)
+        
+        renpy.ui.close()
+
+        renpy.ui.screen = old_ui_screen
+        _current_screen = old_screen
+        
+        # Visit all the children, to get them started.
+        self.child.visit_all(lambda c : c.per_interact())
+
+        # Finish up.
+        self.old_widgets = None
+        self.old_transforms = None
+        self.old_transfers = True
+        
+        if self.current_transform_event:
+
+            for i in self.child.children:
+                i.set_transform_event(self.current_transform_event)
+
+            self.current_transform_event = None
+        
+        return self.widgets
+       
+    def render(self, w, h, st, at):
+        
+        if not self.child:
+            self.update()
+
+        child = renpy.display.render.render(self.child, w, h, st, at)
+
+        rv = renpy.display.render.Render(w, h)
+
+        rv.blit(child, (0, 0), focus=not self.hiding, main=not self.hiding)
+        rv.modal = self.modal and not self.hiding
+        
+        return rv
+        
+    def get_placement(self):
+        if not self.child:
+            self.update()
+
+        return self.child.get_placement()
+
+    def event(self, ev, x, y, st):
+        
+        if self.hiding:
+            return
+        
+        global _current_screen
+        old_screen = _current_screen
+        _current_screen = self
+        
+        rv = self.child.event(ev, x, y, st)
+
+        _current_screen = old_screen
+        
+        if rv is not None:
+            return rv
+
+        if self.modal:
+            raise renpy.display.layout.IgnoreLayers()
+
+
+# The name of the screen that is currently being displayed, or
+# None if no screen is being currently displayed.
+_current_screen = None
+        
+# A map from (screen_name, variant) tuples to screen.
+screens = { }
+
+def get_screen_variant(name):
+    """
+    Get a variant screen object for `name`.
+    """
+
+    for i in renpy.config.variants:
+        rv = screens.get((name, i), None)
+        if rv is not None:
+            return rv
+
+    return None
+
+def define_screen(*args, **kwargs):
+    """
+    :doc: screens
+    :args: (name, function, modal="False", zorder="0", tag=None, variant=None)
+
+    Defines a screen with `name`, which should be a string.
+
+    `function`
+        The function that is called to display the screen. The
+        function is called with the screen scope as keyword
+        arguments. It should ignore additional keyword arguments.
+
+        The function should call the ui functions to add things to the
+        screen.
+
+    `modal`    
+        A string that, when evaluated, determines of the created
+        screen should be modal. A modal screen prevents screens
+        underneath it from receiving input events.
+
+    `zorder`
+        A string that, when evaluated, should be an integer. The integer
+        controls the order in which screens are displayed. A screen
+        with a greater zorder number is displayed above screens with a
+        lesser zorder number.
+
+    `tag`
+        The tag associated with this screen. When the screen is shown,
+        it replaces any other screen with the same tag. The tag
+        defaults to the name of the screen.
+
+    `predict`
+        If true, this screen can be loaded for image prediction. If false,
+        it can't. Defaults to true.
+
+    `variant`
+        String. Gives the variant of the screen to use. 
+
+    """
+    
+    Screen(*args, **kwargs)
+
+
+    
+def get_screen(name, layer="screens"):
+    """
+    :doc: screens
+    
+    Returns the ScreenDisplayable with the given `tag`, on
+    `layer`. If no displayable with the tag is not found, it is
+    interpreted as screen name. If it's still not found, None is returned. 
+     """
+
+    if isinstance(name, str):
+        name = tuple(name.split())
+
+    tag = name[0]
+    
+    sl = renpy.exports.scene_lists()
+
+    sd = sl.get_displayable_by_tag(layer, tag)
+
+    if sd is None:
+        sd = sl.get_displayable_by_name(layer, name)
+        
+    return sd
+
+def has_screen(name):
+    """
+    Returns true if a screen with the given name exists.
+    """
+
+    if not isinstance(name, tuple):
+        name = tuple(name.split())
+
+    if not name:
+        return False
+
+    if get_screen_variant(name[0]):
+        return True
+    else:
+        return False
+
+def show_screen(_screen_name, *_args, **kwargs):
+    """
+    :doc: screens
+    
+    The programmatic equivalent of the show screen statement.
+
+    Shows the named screen. This takes the following keyword arguments:
+
+    `_screen_name`
+        The name of the  screen to show.
+    `_layer`
+        The layer to show the screen on.
+    `_tag`
+        The tag to show the screen with. If not specified, defaults to
+        the tag associated with the screen. It that's not specified,
+        defaults to the name of the screen.,
+    `_widget_properties`
+        A map from the id of a widget to a property name -> property
+        value map. When a widget with that id is shown by the screen,
+        the specified properties are added to it.
+    `_transient`
+        If true, the screen will be automatically hidden at the end of
+        the current interaction.
+
+    Keyword arguments not beginning with underscore (_) are used to
+    initialize the screen's scope.       
+    """
+
+    _layer = kwargs.pop("_layer", "screens")
+    _tag = kwargs.pop("_tag", None)
+    _widget_properties = kwargs.pop("_widget_properties", {})
+    _transient = kwargs.pop("_transient", False)
+
+    name = _screen_name
+    
+    if not isinstance(name, tuple):
+        name = tuple(name.split())
+
+    screen = get_screen_variant(name[0])
+        
+    if screen is None:
+        raise Exception("Screen %s is not known.\n" % (name[0],))
+
+    if _tag is None:
+        _tag = screen.tag
+
+    scope = { }
+    
+    if screen.parameters:
+        scope["_kwargs" ] = kwargs
+        scope["_args"] = _args
+    else:
+        scope.update(kwargs)
+    
+    d = ScreenDisplayable(screen, _tag, _layer, _widget_properties, scope)    
+    renpy.exports.show(name, tag=_tag, what=d, layer=_layer, zorder=d.zorder, transient=_transient, munge_name=False)
+
+
+def predict_screen(_screen_name, *_args, **kwargs):
+    """
+    Predicts the displayables that make up the given screen.
+
+    `_screen_name`
+        The name of the  screen to show.
+    `_widget_properties`
+        A map from the id of a widget to a property name -> property
+        value map. When a widget with that id is shown by the screen,
+        the specified properties are added to it.
+
+    Keyword arguments not beginning with underscore (_) are used to
+    initialize the screen's scope.       
+    """
+
+    _widget_properties = kwargs.pop("_widget_properties", {})
+    _scope = kwargs.pop
+
+    kwargs["_kwargs" ] = kwargs.copy()
+    kwargs["_args"] = _args
+
+    name = _screen_name
+
+    if renpy.config.debug_image_cache:
+        renpy.display.ic_log.write("Predict screen %s", name)
+    
+    if not isinstance(name, tuple):
+        name = tuple(name.split())
+
+    screen = get_screen_variant(name[0])
+
+    scope = { }
+
+    if screen.parameters:
+        scope["_kwargs" ] = kwargs
+        scope["_args"] = _args
+    else:
+        scope.update(kwargs)
+        
+    try:
+    
+        if screen is None:
+            raise Exception("Screen %s is not known.\n" % (name[0],))
+
+        if not screen.predict:
+            return
+
+        d = ScreenDisplayable(screen, None, None, _widget_properties, scope)    
+
+        d.update()
+        renpy.display.predict.displayable(d)
+
+    except:
+        if renpy.config.debug_image_cache:
+            import traceback
+
+            print("While predicting screen", screen)
+            traceback.print_exc()
+
+    renpy.ui.reset()
+            
+
+def hide_screen(tag, layer='screens'):
+    """
+    :doc: screens
+
+    The programmatic equivalent of the hide screen statement.
+    
+    Hides the screen with `tag` on `layer`.
+    """
+
+    screen = get_screen(tag, layer)
+
+    if screen is not None:
+        renpy.exports.hide(screen.tag, layer=layer)
+
+def use_screen(_screen_name, *_args, **kwargs):
+    
+    _name = kwargs.pop("_name", ())
+    _scope = kwargs.pop("_scope", { })
+    
+    name = _screen_name
+    
+    if not isinstance(name, tuple):
+        name = tuple(name.split())
+    
+    screen = get_screen_variant(name[0])
+
+    if screen is None:
+        raise Exception("Screen %r is not known." % name)
+
+    old_transfers = _current_screen.old_transfers
+    _current_screen.old_transfers = True
+        
+    scope = _scope.copy()
+
+    if screen.parameters:
+        scope["_kwargs"] = kwargs
+        scope["_args"] = _args
+    else:
+        scope.update(kwargs)
+    
+    scope["_scope"] = scope
+    scope["_name"] = (_name, name)
+
+    screen.function(**scope)
+
+    _current_screen.old_transfers = old_transfers
+    
+def current_screen():
+    return _current_screen
+
+def get_widget(screen, id, layer='screens'): #@ReservedAssignment
+    """
+    :doc: screens
+
+    From the `screen` on `layer`, returns the widget with
+    `id`. Returns None if the screen doesn't exist, or there is no
+    widget with that id on the screen.
+    """
+
+    if screen is None:
+        screen = current_screen()
+    else:    
+        screen = get_screen(screen, layer)
+
+    if not isinstance(screen, ScreenDisplayable):
+        return None
+
+    if screen.child is None:
+        screen.update()
+    
+    rv = screen.widgets.get(id, None)        
+    return rv
+
+def before_restart():
+    """
+    This is called before Ren'Py restarts to put the screens into restart
+    mode, which prevents crashes due to variables being used that are no
+    longer defined.
+    """
+
+    for k, layer in renpy.display.interface.old_scene.items():
+        if k is None:
+            continue
+        
+        for i in layer.children:
+            if isinstance(i, ScreenDisplayable):
+                i.restarting = True
+            
diff --git a/unrpyc/renpy/display/swdraw.py b/unrpyc/renpy/display/swdraw.py
new file mode 100644
index 0000000..d3ecb51
--- /dev/null
+++ b/unrpyc/renpy/display/swdraw.py
@@ -0,0 +1,1102 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy.display
+import pygame
+import math
+import weakref
+import time
+import os
+
+from renpy.display.render import blit_lock, IDENTITY, BLIT, DISSOLVE, IMAGEDISSOLVE, PIXELLATE
+
+# A map from cached surface to rle version of cached surface.
+rle_cache = weakref.WeakKeyDictionary()
+
+class Clipper(object):
+    """
+    This is used to calculate the clipping rectangle and update rectangles
+    used for a particular draw of the screen.
+    """
+
+    def __init__(self):
+
+        # Lists of (x0, y0, x1, y1, clip, surface, transform) tuples,
+        # representing how a displayable is drawn to the screen.
+        self.blits = [ ]
+        self.old_blits = [ ]
+
+        # Sets of (x0, y0, x1, y1) tuples, representing areas that
+        # aren't part of any displayable.
+        self.forced = set()
+        self.old_forced = set()
+        
+        # The set of surfaces that have been mutated recently.
+        self.mutated = set()
+
+    def compute(self, full_redraw):
+        """
+        This returns a clipping rectangle, and a list of update rectangles
+        that cover the changes between the old and new frames.
+        """
+
+        # First, get things out of the fields, and update them. This
+        # allows us to just return without having to do any cleanup
+        # code.
+        bl0 = self.old_blits
+        bl1 = self.blits
+        old_forced = self.old_forced
+        forced = self.forced
+        mutated = self.mutated
+
+        self.old_blits = bl1
+        self.blits = [ ]
+        self.old_forced = forced
+        self.forced = set()
+        self.mutated = set()
+
+        sw = renpy.config.screen_width
+        sh = renpy.config.screen_height
+        sa = sw * sh
+
+        # A tuple representing the size of the fullscreen.
+        fullscreen = (0, 0, sw, sh)
+        
+        # Check to see if a full redraw has been forced, and return
+        # early.
+        if full_redraw:
+            return fullscreen, [ fullscreen ]
+                    
+        # Quick checks to see if a dissolve is happening, or something like
+        # that.
+        changes = forced | old_forced
+        
+        if fullscreen in changes:
+            return fullscreen, [ fullscreen ]
+
+        # Compute the differences between the two sets, and add those
+        # to changes.
+        i0 = 0
+        i1 = 0
+        bl1set = set(bl1)
+        
+        while True:
+            if i0 >= len(bl0) or i1 >= len(bl1):
+                break
+
+            b0 = bl0[i0]
+            b1 = bl1[i1]
+            
+            if b0 == b1:
+                if id(b0[5]) in mutated:
+                    changes.add(b0[:5])
+
+                i0 += 1
+                i1 += 1
+
+            elif b0 not in bl1set:
+                changes.add(b0[:5])
+                i0 += 1
+
+            else:
+                changes.add(b1[:5])
+                i1 += 1
+
+        changes.update(i[:5] for i in bl0[i0:])
+        changes.update(i[:5] for i in bl1[i1:])
+
+        # No changes? Quit.
+        if not changes:
+            return None, [ ]
+
+        # Compute the sizes of the updated rectangles.        
+        sized = [ ]
+
+        for x0, y0, x1, y1, (sx0, sy0, sx1, sy1) in changes:
+
+            # Round up by a pixel, to prevent visual artifacts when scaled down.
+            x1 += 1
+            y1 += 1
+
+            if x0 < sx0:
+                x0 = sx0
+            if y0 < sy0:
+                y0 = sy0
+            if x1 > sx1:
+                x1 = sx1
+            if y1 > sy1:
+                y1 = sy1
+
+            w = x1 - x0
+            h = y1 - y0
+
+            if w <= 0 or h <= 0:
+                continue
+
+            area = w * h
+
+            if area >= sa:
+                return fullscreen, [ fullscreen ]
+            
+            sized.append((area, x0, y0, x1, y1))
+
+        sized.sort()
+            
+        # The list of non-contiguous updates.
+        noncont = [ ]
+
+        # The total area of noncont.
+        nca = 0
+
+        # Pick the largest area, merge with all overlapping smaller areas, repeat
+        # until no merge possible.
+        while sized:
+            area, x0, y0, x1, y1 = sized.pop()
+
+            
+            merged = False
+            
+            if nca + area >= sa:
+                return (0, 0, sw, sh), [ (0, 0, sw, sh) ]
+
+            i = 0
+
+            while i < len(sized):
+                _iarea, ix0, iy0, ix1, iy1 = sized[i] 
+
+                if (x0 <= ix0 <= x1 or x0 <= ix1 <= x1) and \
+                   (y0 <= iy0 <= y1 or y0 <= iy1 <= y1):
+
+                    merged = True
+                    x0 = min(x0, ix0)
+                    x1 = max(x1, ix1)
+                    y0 = min(y0, iy0)
+                    y1 = max(y1, iy1)
+
+                    area = (x1 - x0) * (y1 - y0)
+
+                    sized.pop(i)
+
+                else:
+                    i += 1
+                    
+            if merged:
+                sized.append((area, x0, y0, x1, y1))                
+            else:
+                noncont.append((x0, y0, x1, y1))
+                nca += area
+
+        if not noncont:
+            return None, [ ]
+                
+        x0, y0, x1, y1 = noncont.pop()
+        x0 = int(x0)
+        y0 = int(y0)
+        x1 = int(math.ceil(x1))
+        y1 = int(math.ceil(y1))
+
+        # A list of (x, y, w, h) tuples for each update.
+        updates = [ (x0, y0, x1 - x0, y1 - y0) ]
+
+        for ix0, iy0, ix1, iy1 in noncont:
+
+            ix0 = int(ix0)
+            iy0 = int(iy0)
+            ix1 = int(math.ceil(ix1))
+            iy1 = int(math.ceil(iy1))
+            
+            x0 = min(x0, ix0)
+            y0 = min(y0, iy0)
+            x1 = max(x1, ix1)
+            y1 = max(y1, iy1)
+
+            updates.append((ix0, iy0, ix1 - ix0, iy1 - iy0))
+
+        return (x0, y0, x1 - x0, y1 - y0), updates
+            
+clippers = [ Clipper() ]        
+
+def surface(w, h, alpha):
+    """
+    Creates a surface that shares a pixel format with the screen. The created
+    surface will 
+    """
+        
+    if alpha:
+        rv = pygame.Surface((w + 4, h + 4), pygame.SRCALPHA)
+    else:
+        rv = pygame.Surface((w + 4, h + 4), 0)
+        
+    return rv.subsurface((2, 2, w, h))
+
+def copy_surface(surf):
+    w, h = surf.get_size()
+    rv = surface(w, h, True)
+
+    renpy.display.accelerator.nogil_copy(surf, rv) # @UndefinedVariable
+    return rv
+
+def draw_special(what, dest, x, y):
+    """
+    This handles the special drawing operations, such as dissolve and
+    image dissolve. `x` and `y` are the offsets of the thing to be drawn
+    relative to the destination rectangle, and are always negative.
+    """
+
+    dw, dh = dest.get_size()
+
+    w = min(dw, what.width + x)
+    h = min(dh, what.height + y)
+
+    if w <= 0 or h <= 0:
+        return
+
+    if what.operation == DISSOLVE:
+    
+        bottom = what.children[0][0].render_to_texture(True)
+        top = what.children[1][0].render_to_texture(True)
+                
+        if what.operation_alpha:
+            target = surface(w, h, True)
+        else:
+            target = dest.subsurface((0, 0, w, h))
+        
+        renpy.display.module.blend(
+            bottom.subsurface((-x, -y, w, h)),
+            top.subsurface((-x, -y, w, h)),
+            target,
+            int(what.operation_complete * 255))
+
+        if what.operation_alpha:
+            dest.blit(target, (0, 0))
+
+    elif what.operation == IMAGEDISSOLVE:
+
+        image = what.children[0][0].render_to_texture(True)
+        bottom = what.children[1][0].render_to_texture(True)
+        top = what.children[2][0].render_to_texture(True)
+
+        if what.operation_alpha:
+            target = surface(w, h, True)
+        else:
+            target = dest.subsurface((0, 0, w, h))
+
+        ramplen = what.operation_parameter
+            
+        ramp = "\x00" * 256
+
+        for i in range(0, ramplen):
+            ramp += chr(255 * i / ramplen)
+
+        ramp += "\xff" * 256
+
+        step = int( what.operation_complete * (256 + ramplen) )
+        ramp = ramp[step:step+256]
+            
+        renpy.display.module.imageblend(
+            bottom.subsurface((-x, -y, w, h)),
+            top.subsurface((-x, -y, w, h)),
+            target,
+            image.subsurface((-x, -y, w, h)),
+            ramp)
+
+        if what.operation_alpha:
+            dest.blit(target, (0, 0))
+
+    elif what.operation == PIXELLATE:
+
+        surf = what.children[0][0].render_to_texture(False)
+
+        px = what.operation_parameter
+
+        renpy.display.module.pixellate(
+            surf.subsurface((-x, -y, w, h)),
+            dest.subsurface((0, 0, w, h)),
+            px, px, px, px)
+    
+    else:
+        raise Exception("Unknown operation: %d" % what.operation)
+
+
+def draw(dest, clip, what, xo, yo, screen):
+    """
+    This is the simple draw routine, which only works when alpha is 1.0
+    and the matrices are None. If those aren't the case, draw_complex
+    is used instead.
+
+    `dest` - Either a destination surface, or a clipper.
+    `clip` - If None, we should draw. Otherwise we should clip, and this is
+    the rectangle to clip to.
+    `what` - The Render or Surface we're drawing to.
+    `xo` - The X offset.
+    `yo` - The Y offset.
+    `screen` - True if this is a blit to the screen, False otherwise.    
+    """
+
+    if not isinstance(what, renpy.display.render.Render):
+
+        # Pixel-Aligned blit.
+        if isinstance(xo, int) and isinstance(yo, int):
+            if screen:
+                what = rle_cache.get(what, what)
+
+            if clip:
+                w, h = what.get_size()
+                dest.blits.append((xo, yo, xo + w, yo + h, clip, what, None))
+            else:
+                try:
+                    blit_lock.acquire()
+                    dest.blit(what, (xo, yo))
+                finally:
+                    blit_lock.release()
+            
+        # Subpixel blit.
+        else:
+            if clip:
+                w, h = what.get_size()
+                dest.blits.append((xo, yo, xo + w, yo + h, clip, what, None))
+            else:            
+                renpy.display.module.subpixel(what, dest, xo, yo)
+
+        return
+
+    # Deal with draw functions.
+    if what.operation != BLIT:
+
+        xo = int(xo)
+        yo = int(yo)
+
+        if clip:
+            dx0, dy0, dx1, dy1 = clip
+            dw = dx1 - dx0
+            dh = dy1 - dy0
+        else:
+            dw, dh = dest.get_size()
+        
+        if xo >= 0:
+            newx = 0
+            subx = xo
+        else:
+            newx = xo
+            subx = 0
+
+        if yo >= 0:
+            newy = 0
+            suby = yo
+        else:
+            newy = yo
+            suby = 0
+
+        if subx >= dw or suby >= dh:
+            return
+
+        # newx and newy are the offset of this render relative to the
+        # subsurface. They can only be negative or 0, as otherwise we
+        # would make a smaller subsurface.
+
+        subw = min(dw - subx, what.width + newx) 
+        subh = min(dh - suby, what.height + newy)
+
+        if subw <= 0 or subh <= 0:
+            return
+
+        if clip:
+            dest.forced.add((subx, suby, subx + subw, suby + subh, clip))
+        else:
+            newdest = dest.subsurface((subx, suby, subw, subh))
+            # what.draw_func(newdest, newx, newy)
+            draw_special(what, newdest, newx, newy)
+
+            
+        return
+
+    # Deal with clipping, if necessary.
+    if what.clipping:
+
+        if clip:
+            cx0, cy0, cx1, cy1 = clip
+
+            cx0 = max(cx0, xo)
+            cy0 = max(cy0, yo)
+            cx1 = min(cx1, xo + what.width)
+            cy1 = min(cy1, yo + what.height)
+
+            if cx0 > cx1 or cy0 > cy1:
+                return
+            
+            clip = (cx0, cy0, cx1, cy1)
+
+            dest.forced.add(clip + (clip,))
+            return
+            
+        else:
+
+            # After this code, x and y are the coordinates of the subsurface
+            # relative to the destination. xo and yo are the offset of the
+            # upper-left corner relative to the subsurface.
+            
+            if xo >= 0:
+                x = xo
+                xo = 0
+            else:
+                x = 0
+                # xo = xo 
+
+            if yo >= 0:
+                y = yo
+                yo = 0
+            else:
+                y = 0
+                # yo = yo 
+
+            dw, dh = dest.get_size()
+
+            width = min(dw - x, what.width + xo)
+            height = min(dh - y, what.height + yo)
+
+            if width < 0 or height < 0:
+                return
+            
+            dest = dest.subsurface((x, y, width, height))
+        
+    # Deal with alpha and transforms by passing them off to draw_transformed.
+    if what.alpha != 1 or (what.forward is not None and what.forward is not IDENTITY):
+        for child, cxo, cyo, _focus, _main in what.visible_children:
+            draw_transformed(dest, clip, child, xo + cxo, yo + cyo,
+                             what.alpha, what.forward, what.reverse)
+        return
+        
+    for child, cxo, cyo, _focus, _main in what.visible_children:
+        draw(dest, clip, child, xo + cxo, yo + cyo, screen)
+
+def draw_transformed(dest, clip, what, xo, yo, alpha, forward, reverse):
+
+    # If our alpha has hit 0, don't do anything.
+    if alpha <= 0.003: # (1 / 256)
+        return
+    
+    if forward is None:
+        forward = IDENTITY
+        reverse = IDENTITY
+    
+    if not isinstance(what, renpy.display.render.Render):
+
+        # Figure out where the other corner of the transformed surface
+        # is on the screen.
+        sw, sh = what.get_size()
+        if clip:
+
+            dx0, dy0, dx1, dy1 = clip
+            dw = dx1 - dx0
+            dh = dy1 - dy0
+
+        else:
+            dw, dh = dest.get_size()
+        
+        x0, y0 = 0.0, 0.0
+        x1, y1 = reverse.transform(sw, 0.0)
+        x2, y2 = reverse.transform(sw, sh)
+        x3, y3 = reverse.transform(0.0, sh)
+
+        minx = math.floor(min(x0, x1, x2, x3) + xo)
+        maxx = math.ceil(max(x0, x1, x2, x3) + xo)
+        miny = math.floor(min(y0, y1, y2, y3) + yo)
+        maxy = math.ceil(max(y0, y1, y2, y3) + yo)
+
+        if minx < 0:
+            minx = 0
+        if miny < 0:
+            miny = 0
+
+        if maxx > dw:
+            maxx = dw
+        if maxy > dh:
+            maxy = dh
+
+        if minx > dw or miny > dh or maxx < 0 or maxy < 0:
+            return
+
+        cx, cy = forward.transform(minx - xo, miny - yo)
+
+        if clip:
+
+            dest.blits.append(
+                (minx, miny, maxx + dx0, maxy + dy0, clip, what,
+                 (cx, cy,
+                  forward.xdx, forward.ydx,
+                  forward.xdy, forward.ydy,
+                  alpha)))
+
+        else:
+
+            dest = dest.subsurface((minx, miny, maxx - minx, maxy - miny))
+            
+            renpy.display.module.transform(
+                what, dest,
+                cx, cy,
+                forward.xdx, forward.ydx,
+                forward.xdy, forward.ydy,
+                alpha, True)
+
+        return
+
+    if what.clipping:
+
+        if reverse.xdy or reverse.ydx:        
+            draw_transformed(dest, clip, what.pygame_surface(True), xo, yo, alpha, forward, reverse)
+            return
+        
+        width = what.width * reverse.xdx
+        height = what.height * reverse.ydy
+
+        if clip:
+            cx0, cy0, cx1, cy1 = clip
+
+            cx0 = max(cx0, xo)
+            cy0 = max(cy0, yo)
+            cx1 = min(cx1, xo + width)
+            cy1 = min(cy1, yo + height)
+
+            if cx0 > cx1 or cy0 > cy1:
+                return
+            
+            clip = (cx0, cy0, cx1, cy1)
+
+            dest.forced.add(clip + (clip,))
+            return
+
+        else:
+
+            # After this code, x and y are the coordinates of the subsurface
+            # relative to the destination. xo and yo are the offset of the
+            # upper-left corner relative to the subsurface.
+            
+            if xo >= 0:
+                x = xo
+                xo = 0
+            else:
+                x = 0
+                # xo = xo 
+
+            if yo >= 0:
+                y = yo
+                yo = 0
+            else:
+                y = 0
+                # yo = yo 
+
+            dw, dh = dest.get_size()
+
+            width = min(dw - x, width + xo)
+            height = min(dh - y, height + yo)
+
+            if width < 0 or height < 0:
+                return
+
+            dest = dest.subsurface((x, y, width, height))
+        
+    if what.draw_func or what.operation != BLIT:
+        child = what.pygame_surface(True)
+        draw_transformed(dest, clip, child, xo, yo, alpha, forward, reverse)
+        return
+
+    for child, cxo, cyo, _focus, _main in what.visible_children:
+
+        cxo, cyo = reverse.transform(cxo, cyo)
+
+        if what.forward:
+            child_forward = forward * what.forward
+            child_reverse = what.reverse * reverse
+        else:
+            child_forward = forward
+            child_reverse = reverse
+            
+        draw_transformed(dest, clip, child, xo + cxo, yo + cyo, alpha * what.alpha, child_forward, child_reverse)
+
+
+
+def do_draw_screen(screen_render, full_redraw, swdraw):
+    """
+    Draws the render produced by render_screen to the screen.
+    """
+
+    yoffset = xoffset = 0    
+    
+    screen_render.is_opaque()
+
+    clip = (xoffset, yoffset, xoffset + screen_render.width, yoffset + screen_render.height)
+    clipper = clippers[0]
+
+    draw(clipper, clip, screen_render, xoffset, yoffset, True)
+
+    cliprect, updates = clipper.compute(full_redraw)
+
+    if cliprect is None:
+        return [ ]
+
+    x, y, _w, _h = cliprect
+
+    dest = swdraw.window.subsurface(cliprect)
+    draw(dest, None, screen_render, -x, -y, True)
+
+    return updates
+
+
+class SWDraw(object):
+    """
+    This uses the software renderer to draw to the screen.
+    """
+
+    def __init__(self):
+        self.display_info = None
+
+        self.reset()
+
+    def reset(self):
+        
+        # Should we draw the screen?
+        self.suppressed_blit = False
+
+        # The earliest time at which the next frame can be redrawn.
+        self.next_frame = 0
+
+        # Mouse re-drawing.
+        self.mouse_location = None
+        self.mouse_backing = None
+        self.mouse_backing_pos = None
+        self.mouse_info = None
+        
+        
+        # Is the mouse currently visible?
+        self.mouse_old_visible = None
+
+        # This is used to cache the surface->texture operation.
+        self.texture_cache = weakref.WeakKeyDictionary()
+
+        # This is used to display video to the screen.
+        self.fullscreen_surface = None
+
+        # Info.
+        self.info = { "renderer" : "sw", "resizable" : False }
+
+        pygame.display.init()
+        renpy.display.interface.post_init()
+
+        if self.display_info is None:       
+            self.display_info = pygame.display.Info()
+
+        # The scale factor we use for this display.
+        self.scale_factor = 1.0
+        
+        # Should we scale fast, or scale good-looking?
+        self.scale_fast = "RENPY_SCALE_FAST" in os.environ
+        
+        # The screen returned to us from pygame.
+        self.screen = None
+        
+        # The window that we render into, if not the screen. This has a 
+        # 1px border around it iff we're scaling.
+        self.window = None
+                
+    def set_mode(self, virtual_size, physical_size, fullscreen):
+
+        # Reset before resize.
+        renpy.display.interface.kill_textures_and_surfaces()
+        self.reset()
+
+        width, height = virtual_size
+
+        # Set up scaling, if necessary.
+        screen_width = self.display_info.current_w
+        screen_height = self.display_info.current_h
+
+        if not fullscreen:
+            screen_height -= 102
+            screen_width -= 102
+
+        scale_factor = min(1.0 * screen_width / width, 1.0 * screen_height / height, 1.0)
+        if "RENPY_SCALE_FACTOR" in os.environ:
+            scale_factor = float(os.environ["RENPY_SCALE_FACTOR"])
+        self.scale_factor = scale_factor
+
+        # Figure out the fullscreen info.
+        if fullscreen:
+            fsflag = pygame.FULLSCREEN
+        else:
+            fsflag = 0
+              
+        # If a window exists of the right size and flags, use it. Otherwise,
+        # make our own window.
+        old_screen = pygame.display.get_surface()
+
+        scaled_width = int(width * scale_factor)
+        scaled_height = int(height * scale_factor)
+
+        if ((old_screen is not None) and 
+            (old_screen.get_size() == (scaled_width, scaled_height)) and
+            (old_screen.get_flags() & pygame.FULLSCREEN == fsflag)):
+            
+            self.screen = old_screen
+                    
+        else:
+            self.screen = pygame.display.set_mode((scaled_width, scaled_height), fsflag, 32)
+            
+        if scale_factor != 1.0:
+            self.window = surface(width, height, True)
+        else:
+            self.window = self.screen
+            
+        renpy.display.pgrender.set_rgba_masks()
+            
+        # Should we redraw the screen from scratch?
+        self.full_redraw = True
+
+        # The surface used to display fullscreen video.
+        self.fullscreen_surface = self.screen
+
+        # Reset this on a mode change.
+        self.mouse_location = None
+        self.mouse_backing = None
+        self.mouse_backing_pos = None
+        self.mouse_info = None
+
+        return True
+
+    # private
+    def show_mouse(self, pos, info):
+        """
+        Actually shows the mouse.
+        """
+
+        self.mouse_location = pos
+        self.mouse_info = info
+
+        mxo, myo, tex = info
+        
+        mx, my = pos
+        mw, mh = tex.get_size()
+
+        bx = mx - mxo
+        by = my - myo
+
+        self.mouse_backing_pos = (bx, by)
+        self.mouse_backing = surface(mw, mh, False)
+        self.mouse_backing.blit(self.window, (0, 0), (bx, by, mw, mh))
+
+        self.screen.blit(tex, (bx, by))
+
+        return bx, by, mw, mh
+
+    # private
+    def hide_mouse(self):
+        """
+        Actually hides the mouse.
+        """
+        
+        size = self.mouse_backing.get_size()
+        self.screen.blit(self.mouse_backing, self.mouse_backing_pos)
+
+        rv = self.mouse_backing_pos + size
+
+        self.mouse_backing = None
+        self.mouse_backing_pos = None
+        self.mouse_location = None 
+
+        return rv
+
+    # private
+    def draw_mouse(self, show_mouse):
+        """
+        This draws the mouse to the screen, if necessary. It uses the
+        buffer to minimize the amount of the screen that needs to be
+        drawn, and only redraws if the mouse has actually been moved.
+        """
+
+        hardware, x, y, tex = renpy.game.interface.get_mouse_info()
+        
+        if self.mouse_old_visible != hardware:
+            pygame.mouse.set_visible(hardware)
+            self.mouse_old_visible = hardware
+
+        # The rest of this is for the software mouse.
+        
+        if self.suppressed_blit:
+            return [ ]
+
+        if not show_mouse:
+            tex = None
+
+        info = (x, y, tex)
+        pos = pygame.mouse.get_pos()
+            
+        if (pos == self.mouse_location and tex and info == self.mouse_info):
+            return [ ]
+
+        updates = [ ]
+
+        if self.mouse_location:
+            updates.append(self.hide_mouse())
+            
+        if tex and pos and renpy.game.interface.focused:
+            updates.append(self.show_mouse(pos, info))
+            
+        return updates
+
+    def update_mouse(self):
+        """
+        Draws the mouse, and then updates the screen.
+        """
+        
+        updates = self.draw_mouse(True)
+
+        if updates:
+            pygame.display.update(updates)
+            
+    def mouse_event(self, ev):        
+        x, y = getattr(ev, 'pos', pygame.mouse.get_pos())
+        
+        x /= self.scale_factor
+        y /= self.scale_factor
+        
+        return x, y
+        
+    def get_mouse_pos(self):
+        x, y = pygame.mouse.get_pos()
+        
+        x /= self.scale_factor
+        y /= self.scale_factor
+        
+        return x, y
+    
+    
+    def screenshot(self, surftree, fullscreen_video):
+        """
+        Returns a pygame surface containing a screenshot.
+        """
+
+        return self.window
+    
+    def should_redraw(self, needs_redraw, first_pass):
+        """
+        Uses the framerate to determine if we can and should redraw.
+        """
+
+        if not needs_redraw:
+            return False
+        
+        framerate = renpy.config.framerate
+
+        if framerate is None:
+            return True
+        
+        next_frame = self.next_frame
+        now = pygame.time.get_ticks()
+
+        frametime = 1000.0 / framerate
+
+        # Handle timer rollover.
+        if next_frame > now + frametime:
+            next_frame = now
+
+        # It's not yet time for the next frame.
+        if now < next_frame and not first_pass:            
+            return False
+            
+        # Otherwise, it is. Schedule the next frame.
+        # if next_frame + frametime < now:
+        next_frame = now + frametime
+        # else:
+        #    next_frame += frametime
+
+        self.next_frame = next_frame
+
+        return True
+
+
+    def draw_screen(self, surftree, fullscreen_video):
+        """
+        Draws the screen. 
+        """
+        
+        if not fullscreen_video:
+
+            updates = [ ]
+
+            updates.extend(self.draw_mouse(False))
+
+            damage = do_draw_screen(surftree, self.full_redraw, self)
+
+            if damage:
+                updates.extend(damage)
+
+            self.full_redraw = False
+
+            if self.window is self.screen:
+
+                updates.extend(self.draw_mouse(True))
+                pygame.display.update(updates)
+
+            else:
+                
+                if self.scale_fast:
+                    pygame.transform.scale(self.window, self.screen.get_size(), self.screen)
+                else:
+                    renpy.display.scale.smoothscale(self.window, self.screen.get_size(), self.screen)
+                
+                self.draw_mouse(True)                
+                pygame.display.flip()
+            
+        else:
+            pygame.display.flip()
+            self.full_redraw = True
+
+        self.suppressed_blit = fullscreen_video
+
+        
+    def render_to_texture(self, render, alpha):
+  
+        rv = surface(render.width, render.height, alpha)
+        draw(rv, None, render, 0, 0, False)
+
+        return rv
+
+    def is_pixel_opaque(self, what, x, y):
+
+        if x < 0 or y < 0 or x >= what.width or y >= what.height:
+            return 0
+
+        for (child, xo, yo, _focus, _main) in what.visible_children:
+            cx = x - xo
+            cy = y - yo
+
+            if what.forward:
+                cx, cy = what.forward.transform(cx, cy)
+
+
+            if isinstance(child, renpy.display.render.Render):
+                if self.is_pixel_opaque(child, x, y):
+                    return True
+
+            else:
+                cx = int(cx)
+                cy = int(cy)
+                
+                cw, ch = child.get_size()
+                if cx >= cw or cy >= ch:
+                    return False
+
+                
+
+                if not child.get_masks()[3] or child.get_at((cx, cy))[3]:
+                    return True
+
+        return False
+
+    
+    def mutated_surface(self, surf):
+        """
+        Called to indicate that the given surface has changed.
+        """
+
+        for i in clippers:
+            i.mutated.add(id(surf))
+
+        if surf in rle_cache:
+            del rle_cache[surf]
+            
+            
+    def load_texture(self, surf, transient=False):
+        """
+        Creates a texture from the surface. In the software implementation,
+        the only difference between a texture and a surface is that a texture
+        is in the RLE cache.
+        """
+
+        surf = copy_surface(surf)
+        self.mutated_surface(surf)
+
+        if transient:
+            return surf
+
+        if renpy.game.less_memory:
+            return surf
+
+        if surf not in rle_cache:
+            rle_surf = copy_surface(surf)
+            rle_surf.set_alpha(255, pygame.RLEACCEL)
+            self.mutated_surface(rle_surf)
+
+            rle_cache[surf] = rle_surf
+        
+        return surf
+
+    def solid_texture(self, w, h, color):
+        """
+        Creates a texture filled to the edges with color.
+        """
+        
+        surf = surface(w + 4, h + 4, True)
+        surf.fill(color)
+        self.mutated_surface(surf)
+
+        surf = surf.subsurface((2, 2, w, h))
+        
+        self.mutated_surface(surf)
+        return surf
+        
+        
+    def free_memory(self):
+        """
+        Frees up memory.
+        """
+
+        rle_cache.clear()
+        
+    def deinit(self):
+        """
+        Called when we're restarted.
+        """
+
+        renpy.display.render.free_memory()
+        
+        return
+        
+    def quit(self): #@ReservedAssignment
+        """
+        Shuts down the drawing system.
+        """
+
+        pygame.display.quit()
+        
+        return
+            
+    def event_peek_sleep(self):
+        """
+        Wait a little bit so the CPU doesn't speed up.
+        """
+
+        time.sleep(.0001)
+
+    def get_physical_size(self):
+        """
+        Return the physical width and height of the screen.
+        """
+        return renpy.config.screen_width, renpy.config.screen_height
diff --git a/unrpyc/renpy/display/transition.py b/unrpyc/renpy/display/transition.py
new file mode 100644
index 0000000..7f93312
--- /dev/null
+++ b/unrpyc/renpy/display/transition.py
@@ -0,0 +1,922 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# NOTE:
+# Transitions need to be able to work even when old_widget and new_widget
+# are None, at least to the point of making it through __init__. This is
+# so that prediction of images works.
+
+import renpy.display
+from renpy.display.render import render
+
+
+class Transition(renpy.display.core.Displayable):
+    """
+    This is the base class of most transitions. It takes care of event
+    dispatching.
+    """
+
+    def __init__(self, delay, **properties):
+        super(Transition, self).__init__(**properties)
+        self.delay = delay
+        self.events = True
+        
+    def event(self, ev, x, y, st):
+
+        if self.events or ev.type == renpy.display.core.TIMEEVENT:
+            return self.new_widget.event(ev, x, y, st) # E1101
+        else:
+            return None
+
+    def visit(self):
+        return [ self.new_widget, self.old_widget ] # E1101
+
+
+def null_render(d, width, height, st, at):
+
+    d.events = True
+    surf = renpy.display.render.render(d.new_widget,
+                                       width,
+                                       height,
+                                       st, at)
+
+    rv = renpy.display.render.Render(surf.width, surf.height)
+    rv.blit(surf, (0, 0))
+    
+    return rv
+    
+class NoTransition(Transition):
+    """
+    :doc: transition function
+    :args: (delay)
+    
+    Returns a transition that only displays the new screen for `delay` seconds.
+    It can be useful as part of a MultipleTransition.
+    """
+
+    def __init__(self, delay, old_widget=None, new_widget=None, **properties):
+        super(NoTransition, self).__init__(delay, **properties)
+
+        self.old_widget = old_widget
+        self.new_widget = new_widget
+        self.events = True
+
+    def render(self, width, height, st, at):
+        return null_render(self, width, height, st, at)
+
+
+class MultipleTransition(Transition):
+    """
+    :doc: transition function
+    :args: (args)
+    
+    Returns a transition that allows multiple transitions to be displayed, one
+    after the other.
+    
+    `args`
+        A list containing an odd number of items. The first, third, and 
+        other odd-numbered items must be scenes, and the even items
+        must be transitions. A scene can be one of:
+        
+        * A displayable.
+        * False, to use the old scene.
+        * True, to use the new scene. 
+
+        Almost always, the first argument will be False and the last True.
+        
+    The transitions in `args` are applied in order. For each transition, 
+    the old scene is the screen preceding it, and the new scene is the 
+    scene following it. For example::
+    
+        define logodissolve = MultipleTransition(
+            False, Dissolve(0.5)
+            "logo.jpg", NoTransition(1.0),
+            "logo.jpg", dissolve,
+            True)
+            
+    This example will dissolve to logo.jpg, wait 1 second, and then 
+    dissolve to the new scene.
+    """
+    
+    def __init__(self, args, old_widget=None, new_widget=None, **properties):
+        
+        if len(args) % 2 != 1 or len(args) < 3:
+            raise Exception("MultipleTransition requires an odd number of arguments, and at least 3 arguments.")
+
+        self.transitions = [ ]
+
+        # The screens that we use for the transition.
+        self.screens = [ renpy.easy.displayable(i) for i in args[0::2] ]
+
+        def oldnew(w):
+            if w is False:
+                return old_widget
+            if w is True:
+                return new_widget
+
+            return w
+
+        for old, trans, new in zip(self.screens[0:], args[1::2], self.screens[1:]):
+            old = oldnew(old)
+            new = oldnew(new)
+
+            self.transitions.append(trans(old_widget=old, new_widget=new))
+
+        super(MultipleTransition, self).__init__(sum([i.delay for i in self.transitions]), **properties)
+
+        self.new_widget = self.transitions[-1]
+        self.events = False
+
+    def visit(self):
+        return [ i for i in self.screens if isinstance(i, renpy.display.core.Displayable)] + self.transitions
+    
+    def event(self, ev, x, y, st):
+
+        if self.events or ev.type == renpy.display.core.TIMEEVENT:
+            return self.transitions[-1].event(ev, x, y, st)
+        else:
+            return None
+    
+    def render(self, width, height, st, at):
+
+        if renpy.game.less_updates:
+            return null_render(self, width, height, st, at)
+
+        for trans in self.transitions[:-1]:
+
+            if trans.delay > st:
+                break
+
+            st -= trans.delay
+            
+        else:
+
+            trans = self.transitions[-1]
+            self.events = True
+            
+        if trans is not self.transitions[-1]:
+            renpy.display.render.render(self.transitions[-1], width, height, 0, 0)
+            
+        surf = renpy.display.render.render(trans, width, height, st, at)
+        width, height = surf.get_size()
+        rv = renpy.display.render.Render(width, height)
+        rv.blit(surf, (0, 0))
+        
+        if st < trans.delay:
+            renpy.display.render.redraw(self, trans.delay - st)
+
+        return rv
+            
+
+def Fade(out_time,
+         hold_time,
+         in_time,
+         old_widget=None,
+         new_widget=None,
+         color=None,
+         widget=None,
+         alpha=False,
+         ):
+
+    """
+    :doc: transition function
+    :args: (out_time, hold_time, in_time, color="#000")
+    :name: Fade
+    
+    Returns a transition that takes `out_time` seconds to fade to 
+    a screen filled with `color`, holds at that screen for `hold_time`
+    seconds, and then takes `in_time` to fade to then new screen.
+    
+    ::
+    
+        # Fade to black and back.
+        define fade = Fade(0.5, 0.0, 0.5)
+        
+        # Hold at black for a bit.
+        define fadehold = Fade(0.5, 1.0, 0.5)
+        
+        # Camera flash - quickly fades to white, then back to the scene.
+        define flash = Fade(0.1, 0.0, 0.5, color="#fff")
+    """
+
+    dissolve = renpy.curry.curry(Dissolve)
+    notrans = renpy.curry.curry(NoTransition)
+
+    widget = renpy.easy.displayable_or_none(widget)
+    
+    if color:
+        widget = renpy.display.image.Solid(color)
+    
+    if not widget:
+        widget = renpy.display.image.Solid((0, 0, 0, 255))
+
+    args = [ False, dissolve(out_time, alpha=alpha), widget ]
+
+    if hold_time:
+        args.extend([ notrans(hold_time), widget, ])
+
+    args.extend([dissolve(in_time, alpha=alpha), True ])
+
+    return MultipleTransition(args, old_widget=old_widget, new_widget=new_widget)
+
+
+class Pixellate(Transition):
+    """
+    :doc: transition function
+    :args: (time, steps)
+    :name: Pixellate
+    
+    Returns a transition that pixellates out the old screen, and then
+    pixellates in the new screen.
+    
+    `time`
+        The total time the transition will take, in seconds.
+
+    `steps`
+        The number of steps that will occur, in each direction. Each step
+        creates pixels about twice the size of those in the previous step,
+        so a 5-step pixellation will create 32x32 pixels.
+    """
+
+    def __init__(self, time, steps, old_widget=None, new_widget=None, **properties):
+
+        time = float(time)
+
+        super(Pixellate, self).__init__(time, **properties)
+
+        self.time = time
+        self.steps = steps
+
+        self.old_widget = old_widget
+        self.new_widget = new_widget
+
+        self.events = False
+
+        self.quantum = time / (2 * steps)
+
+    def render(self, width, height, st, at):
+
+        if renpy.game.less_updates:
+            return null_render(self, width, height, st, at)
+
+        if st >= self.time:
+            self.events = True
+            return render(self.new_widget, width, height, st, at)
+
+        step = st // self.quantum + 1
+        visible = self.old_widget
+
+        if step > self.steps:
+            step = (self.steps * 2) - step + 1
+            visible = self.new_widget
+            self.events = True
+
+
+        rdr = render(visible, width, height, st, at)
+        rv = renpy.display.render.Render(rdr.width, rdr.height)
+
+        rv.blit(rdr, (0, 0))
+
+        rv.operation = renpy.display.render.PIXELLATE
+        rv.operation_parameter = 2 ** step
+        
+        renpy.display.render.redraw(self, 0)
+
+        return rv
+
+        
+class Dissolve(Transition):
+    """
+    :doc: transition function
+    :args: (time, alpha=False, time_warp=None)
+    :name: Dissolve
+    
+    Returns a transition that dissolves from the old scene to the new scene.
+    
+    `time`
+        The time the dissolve will take.
+
+    `alpha`
+        If true, the dissolve will alpha-composite the the result of the 
+        transition with the screen. If false, the result of the transition
+        will replace the screen, which is more efficient.
+
+    `time_warp`
+        A function that adjusts the timeline. If not None, this should be a 
+        function that takes a fractional time between 0.0 and 1.0, and returns 
+        a number in the same range. 
+    """
+
+    __version__ = 1
+
+    def after_upgrade(self, version):
+        if version < 1:
+            self.alpha = False
+    
+    time_warp = None
+    
+    def __init__(self, time, old_widget=None, new_widget=None, alpha=False, time_warp=None, **properties):
+        super(Dissolve, self).__init__(time, **properties)
+
+        self.time = time
+        self.old_widget = old_widget
+        self.new_widget = new_widget
+        self.events = False
+        self.alpha = alpha
+        self.time_warp = time_warp
+
+
+    def render(self, width, height, st, at):
+
+        if renpy.game.less_updates:
+            return null_render(self, width, height, st, at)
+                                            
+        if st >= self.time:
+            self.events = True
+            return render(self.new_widget, width, height, st, at)
+
+        complete = min(1.0, st / self.time)
+
+        if self.time_warp is not None:
+            complete = self.time_warp(complete)
+
+        bottom = render(self.old_widget, width, height, st, at)
+        top = render(self.new_widget, width, height, st, at)
+        
+        width = min(top.width, bottom.width)
+        height = min(top.height, bottom.height)
+
+        rv = renpy.display.render.Render(width, height, opaque=not self.alpha)
+
+        rv.operation = renpy.display.render.DISSOLVE        
+        rv.operation_alpha = self.alpha
+        rv.operation_complete = complete
+                
+        rv.blit(bottom, (0, 0), focus=False, main=False)
+        rv.blit(top, (0, 0), focus=True, main=True)
+
+        renpy.display.render.redraw(self, 0)
+
+        return rv
+
+
+class ImageDissolve(Transition):
+    """
+    :doc: transition function
+    :args: (image, time, ramplen=8, reverse=False, alpha=True, time_warp=None)
+    :name: ImageDissolve
+
+    Returns a transition that dissolves the old scene into the new scene, using
+    an image to control the dissolve process. This means that white pixels will
+    dissolve in first, and black pixels will dissolve in last.
+    
+    `image`
+        A control image to use. This must be either an image file or 
+        image manipulator. The control image should be the size of
+        the scenes being dissolved.
+        
+    `time`
+        The time the dissolve will take.
+        
+    `ramplen`
+        The length of the ramp to use. This must be an integer power 
+        of 2. When this is the default value of 8, when a white pixel
+        is fully dissolved, a pixel 8 shades of gray darker will have
+        completed one step of dissolving in.
+        
+    `reverse`
+        If true, black pixels will dissolve in before white pixels.
+        
+    `alpha`
+        If true, the dissolve will alpha-composite the the result of the 
+        transition with the screen. If false, the result of the transition
+        will replace the screen, which is more efficient.
+
+    `time_warp`
+        A function that adjusts the timeline. If not None, this should be a 
+        function that takes a fractional time between 0.0 and 1.0, and returns 
+        a number in the same range.         
+ 
+    ::
+    
+        define circirisout = ImageDissolve("circiris.png", 1.0)
+        define circirisin = ImageDissolve("circiris.png", 1.0, reverse=True)
+        define circiristbigramp = ImageDissolve("circiris.png", 1.0, ramplen=256)
+    """
+
+    __version__ = 1
+
+    def after_upgrade(self, version):
+        if version < 1:
+            self.alpha = False
+    
+    time_warp = None
+    
+    def __init__(
+        self,
+        image,
+        time,
+        ramplen=8,
+        ramptype='linear',
+        ramp=None,
+        reverse=False,
+        alpha=False,
+        old_widget=None,
+        new_widget=None,
+        time_warp=None,
+        **properties):
+
+        # ramptype and ramp are now unused, but are kept for compatbility with
+        # older code.
+        
+        super(ImageDissolve, self).__init__(time, **properties)
+
+        self.old_widget = old_widget
+        self.new_widget = new_widget
+        self.events = False
+        self.alpha = alpha
+        self.time_warp = time_warp
+        
+        if not reverse:
+
+            # Copies red -> alpha
+            matrix = renpy.display.im.matrix(
+                0, 0, 0, 0, 1,
+                0, 0, 0, 0, 1,
+                0, 0, 0, 0, 1,
+                1, 0, 0, 0, 0)
+
+        else:
+
+            # Copies 1-red -> alpha
+            matrix = renpy.display.im.matrix(
+                0, 0, 0, 0, 1,
+                0, 0, 0, 0, 1,
+                0, 0, 0, 0, 1,
+                - 1, 0, 0, 0, 1)
+
+        self.image = renpy.display.im.MatrixColor(image, matrix)
+
+        if ramp is not None:
+            ramplen = len(ramp)
+
+        # The length of the ramp.
+        self.ramplen = max(ramplen, 1)
+
+
+    def visit(self):
+        return super(ImageDissolve, self).visit() + [ self.image ]
+
+    
+    def render(self, width, height, st, at):
+
+        if renpy.game.less_updates or renpy.display.less_imagedissolve:
+            return null_render(self, width, height, st, at)
+
+        if st >= self.delay:
+            self.events = True
+            return render(self.new_widget, width, height, st, at)
+
+        image = render(self.image, width, height, st, at)
+        bottom = render(self.old_widget, width, height, st, at)
+        top = render(self.new_widget, width, height, st, at)
+
+        width = min(bottom.width, top.width, image.width)
+        height = min(bottom.height, top.height, image.height)
+
+        rv = renpy.display.render.Render(width, height, opaque=not self.alpha)
+
+        complete = st / self.delay
+        
+        if self.time_warp is not None:
+            complete = self.time_warp(complete)
+
+        rv.operation = renpy.display.render.IMAGEDISSOLVE
+        rv.operation_alpha = self.alpha
+        rv.operation_complete = complete
+        rv.operation_parameter = self.ramplen
+
+        rv.blit(image, (0, 0), focus=False, main=False)
+        rv.blit(bottom, (0, 0), focus=False, main=False)
+        rv.blit(top, (0, 0), focus=True, main=True)
+        
+        renpy.display.render.redraw(self, 0)
+
+        return rv
+
+
+class AlphaDissolve(Transition):
+    """
+    :doc: transition function
+    :args: (control, delay=0.0, alpha=False, reverse=False)
+    
+    Returns a transition that uses a control displayable (almost always some
+    sort of animated transform) to transition from one screen to another. The
+    transform is evaluated. The new screen is used where the transform is
+    opaque, and the old image is used when it is transparent.
+
+    `control`
+        The control transform.
+
+    `delay`
+        The time the transition takes, before ending.
+
+    `alpha`
+        If true, the image is composited with what's behind it. If false,
+        the default, the image is opaque and overwrites what's behind it.
+
+    `reverse`
+        If true, the alpha channel is reversed. Opaque areas are taken
+        from the old image, while transparent areas are taken from the
+        new image. 
+     """
+
+    def __init__(
+        self,
+        control,
+        delay=0.0,
+        old_widget=None,
+        new_widget=None,
+        alpha=False,
+        reverse=False,
+        **properties):
+
+        super(AlphaDissolve, self).__init__(delay, **properties)
+
+        self.control = renpy.display.layout.Fixed()
+        self.control.add(control)
+        
+        self.old_widget = renpy.easy.displayable(old_widget)
+        self.new_widget = renpy.easy.displayable(new_widget)
+        self.events = False
+        
+        self.alpha = alpha
+        self.reverse = reverse
+        
+    def visit(self):
+        return super(AlphaDissolve, self).visit() + [ self.control ]
+    
+    def render(self, width, height, st, at):
+
+        if renpy.game.less_updates or renpy.display.less_imagedissolve:
+            return null_render(self, width, height, st, at)
+
+        if st >= self.delay:
+            self.events = True
+
+        bottom = render(self.old_widget, width, height, st, at)
+        top = render(self.new_widget, width, height, st, at)
+
+        width = min(bottom.width, top.width)
+        height = min(bottom.height, top.height)
+
+        control = render(self.control, width, height, st, at)
+
+        rv = renpy.display.render.Render(width, height, opaque=not self.alpha)
+
+        rv.operation = renpy.display.render.IMAGEDISSOLVE
+        rv.operation_alpha = self.alpha
+        rv.operation_complete = 256.0 / (256.0 + 256.0)
+        rv.operation_parameter = 256
+
+        rv.blit(control, (0, 0), focus=False, main=False)
+
+        if not self.reverse:
+            rv.blit(bottom, (0, 0), focus=False, main=False)
+            rv.blit(top, (0, 0), focus=True, main=True)
+        else:
+            rv.blit(top, (0, 0), focus=True, main=True)
+            rv.blit(bottom, (0, 0), focus=False, main=False)
+
+        return rv
+    
+
+class CropMove(Transition):
+    """
+    :doc: transition function
+    :args: (time, mode="slideright", startcrop=(0.0, 0.0, 0.0, 1.0), startpos=(0.0, 0.0), endcrop=(0.0, 0.0, 1.0, 1.0), endpos=(0.0, 0.0), topnew=True)
+    :name: CropMove
+
+    Returns a transition that works by cropping a scene and positioning it on the
+    screen. This can be used to implement a variety of effects, all of which 
+    involved changing rectangular slices of scenes. 
+
+    `time`
+        The time the transition takes.
+        
+    `mode`
+        The name of the mode of the transition. There are three groups
+        of modes: wipes, slides, and other. This can also be "custom", 
+        to allow a custom mode to be defined.
+    
+        In a wipe, the image stays fixed, and more of it is revealed as
+        the transition progresses. For example, in "wiperight", a wipe from left to right, first the left edge of the image is
+        revealed at the left edge of the screen, then the center of the image,
+        and finally the right side of the image at the right of the screen.
+        Other supported wipes are "wipeleft", "wipedown", and "wipeup".
+    
+        In a slide, the image moves. So in a "slideright", the right edge of the
+        image starts at the left edge of the screen, and moves to the right
+        as the transition progresses. Other slides are "slideleft", "slidedown",
+        and "slideup".
+    
+        There are also slideaways, in which the old image moves on top of
+        the new image. Slideaways include "slideawayright", "slideawayleft",
+        "slideawayup", and "slideawaydown".
+    
+        We also support a rectangular iris in with "irisin" and a
+        rectangular iris out with "irisout".
+
+    The following parameters are only respected if the mode is "custom". Positions 
+    are relative to the size of the screen, while the crops are relative to the 
+    size of the image. So a crop of (0.25, 0.0, 0.5, 1.0) takes the middle 
+    half of an image.
+
+    `startcrop`    
+        The starting rectangle that is cropped out of the
+        top image. A 4-element tuple containing x, y, width, and height. 
+        
+    `startpos`
+        The starting place that the top image is drawn
+        to the screen at, a 2-element tuple containing x and y.
+
+    `endcrop`
+        The ending rectangle that is cropped out of the
+        top image. A 4-element tuple containing x, y, width, and height. 
+        
+    `endpos`
+        The ending place that the top image is drawn
+        to the screen at, a 2-element tuple containing x and y.
+
+    `topnew`
+        If true, the scene that is cropped and moved (and is on top of 
+        the other scene) is the new scene. If false, it is the old scene.
+
+    ::
+    
+        define wiperight = CropMove(1.0, "wiperight")
+        define wipeleft = CropMove(1.0, "wipeleft")
+        define wipeup = CropMove(1.0, "wipeup")
+        define wipedown = CropMove(1.0, "wipedown")
+    
+        define slideright = CropMove(1.0, "slideright")
+        define slideleft = CropMove(1.0, "slideleft")
+        define slideup = CropMove(1.0, "slideup")
+        define slidedown = CropMove(1.0, "slidedown")
+    
+        define slideawayright = CropMove(1.0, "slideawayright")
+        define slideawayleft = CropMove(1.0, "slideawayleft")
+        define slideawayup = CropMove(1.0, "slideawayup")
+        define slideawaydown = CropMove(1.0, "slideawaydown")
+    
+        define irisout = CropMove(1.0, "irisout")
+        define irisin = CropMove(1.0, "irisin")
+    """
+
+    def __init__(self, time,
+                 mode="slideright",
+                 startcrop=(0.0, 0.0, 0.0, 1.0),
+                 startpos=(0.0, 0.0),
+                 endcrop=(0.0, 0.0, 1.0, 1.0),
+                 endpos=(0.0, 0.0),
+                 topnew=True,
+                 old_widget=None,
+                 new_widget=None,
+                 **properties):
+        
+        super(CropMove, self).__init__(time, **properties)
+        self.time = time
+
+        if mode == "wiperight":
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 0.0, 1.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "wipeleft":
+            startpos = (1.0, 0.0)
+            startcrop = (1.0, 0.0, 0.0, 1.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "wipedown":
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 1.0, 0.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "wipeup":
+            startpos = (0.0, 1.0)
+            startcrop = (0.0, 1.0, 1.0, 0.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "slideright":
+            startpos = (0.0, 0.0)
+            startcrop = (1.0, 0.0, 0.0, 1.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "slideleft":
+            startpos = (1.0, 0.0)
+            startcrop = (0.0, 0.0, 0.0, 1.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "slideup":
+            startpos = (0.0, 1.0)
+            startcrop = (0.0, 0.0, 1.0, 0.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+            
+        elif mode == "slidedown":
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 1.0, 1.0, 0.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+
+        elif mode == "slideawayleft":
+            endpos = (0.0, 0.0)
+            endcrop = (1.0, 0.0, 0.0, 1.0)
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = False
+
+        elif mode == "slideawayright":
+            endpos = (1.0, 0.0)
+            endcrop = (0.0, 0.0, 0.0, 1.0)
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = False
+
+        elif mode == "slideawaydown":
+            endpos = (0.0, 1.0)
+            endcrop = (0.0, 0.0, 1.0, 0.0)
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = False
+            
+        elif mode == "slideawayup":
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 1.0, 1.0, 0.0)
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = False
+
+        elif mode == "irisout":
+            startpos = (0.5, 0.5)
+            startcrop = (0.5, 0.5, 0.0, 0.0)
+            endpos = (0.0, 0.0)
+            endcrop = (0.0, 0.0, 1.0, 1.0)
+            topnew = True
+            
+        elif mode == "irisin":
+            startpos = (0.0, 0.0)
+            startcrop = (0.0, 0.0, 1.0, 1.0)
+            endpos = (0.5, 0.5)
+            endcrop = (0.5, 0.5, 0.0, 0.0)
+            topnew = False
+            
+            
+        elif mode == "custom":
+            pass
+        else:
+            raise Exception("Invalid mode %s passed into CropMove." % mode)
+
+        self.delay = time
+        self.time = time
+
+        self.startpos = startpos
+        self.endpos = endpos
+
+        self.startcrop = startcrop
+        self.endcrop = endcrop
+        
+        self.topnew = topnew
+
+        self.old_widget = old_widget
+        self.new_widget = new_widget
+
+        self.events = False
+
+        if topnew:
+            self.bottom = old_widget
+            self.top = new_widget
+        else:
+            self.bottom = new_widget
+            self.top = old_widget
+
+    def render(self, width, height, st, at):
+
+        if renpy.game.less_updates:
+            return null_render(self, width, height, st, at)
+
+        time = 1.0 * st / self.time
+
+        # Done rendering.
+        if time >= 1.0:
+            self.events = True
+            return render(self.new_widget, width, height, st, at)
+        
+        # How we scale each element of a tuple.
+        scales = (width, height, width, height)
+
+        def interpolate_tuple(t0, t1):
+            return tuple([ int(s * (a * (1.0 - time) + b * time))
+                           for a, b, s in zip(t0, t1, scales) ])
+
+        crop = interpolate_tuple(self.startcrop, self.endcrop)
+        pos = interpolate_tuple(self.startpos, self.endpos)
+
+
+        top = render(self.top, width, height, st, at)
+        bottom = render(self.bottom, width, height, st, at)
+        
+        width = min(bottom.width, width)
+        height = min(bottom.height, height)
+        rv = renpy.display.render.Render(width, height)
+
+        rv.blit(bottom, (0, 0), focus=not self.topnew)
+
+        ss = top.subsurface(crop, focus=self.topnew)
+        rv.blit(ss, pos, focus=self.topnew)
+
+        renpy.display.render.redraw(self, 0)
+        return rv
+
+
+def ComposeTransition(trans, before=None, after=None, new_widget=None, old_widget=None):
+    """
+    :doc: transition function
+    :args: (trans, before, after)
+    
+    Returns a transition that composes up to three transitions. If not None,
+    the `before` and `after` transitions are applied to the old and new 
+    scenes, respectively. These updated old and new scenes are then supplied
+    to the `trans` transition.
+
+    ::
+
+        # Move the images in and out while dissolving. (This is a fairly expensive transition.)
+        define moveinoutdissolve = ComposeTransition(dissolve, before=moveoutleft, after=moveinright)
+    """
+    
+    if before is not None:
+        old = before(new_widget=new_widget, old_widget=old_widget)
+    else:
+        old = old_widget
+        
+    if after is not None:
+        new = after(new_widget=new_widget, old_widget=old_widget)
+    else:
+        new = new_widget
+
+    return trans(new_widget=new, old_widget=old)
+
+
+def SubTransition(rect, trans, old_widget=None, new_widget=None, **properties):
+    """
+    Applies a transition to a subset of the screen. Not documented.
+    """
+    
+    x, y, _w, _h = rect
+
+    old = renpy.display.layout.LiveCrop(rect, old_widget)
+    new = renpy.display.layout.LiveCrop(rect, new_widget)
+
+    inner = trans(old_widget=old, new_widget=new)
+    delay = inner.delay
+    inner = renpy.display.layout.Position(inner, xpos=x, ypos=y, xanchor=0, yanchor=0)
+    
+    f = renpy.display.layout.MultiBox(layout='fixed')
+    f.add(new_widget)
+    f.add(inner)
+
+    return NoTransition(delay, old_widget=f, new_widget=f)
+
diff --git a/unrpyc/renpy/display/video.py b/unrpyc/renpy/display/video.py
new file mode 100644
index 0000000..5451e89
--- /dev/null
+++ b/unrpyc/renpy/display/video.py
@@ -0,0 +1,234 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+import renpy.display
+import renpy.audio
+
+# The movie displayable that's currently being shown on the screen.
+current_movie = None
+
+# True if the movie that is currently displaying is in fullscreen mode,
+# False if it's a smaller size.
+fullscreen = False
+
+# The size of a Movie object that hasn't had an explicit size set.
+default_size = (400, 300)
+
+# The file we allocated the surface for.
+surface_file = None
+
+# The surface to display the movie on, if not fullscreen.
+surface = None
+
+def movie_stop(clear=True, only_fullscreen=False):
+    """
+    Stops the currently playing movie.
+    """
+
+    if (not fullscreen) and only_fullscreen:
+        return
+
+    renpy.audio.music.stop(channel='movie')
+
+    
+def movie_start(filename, size=None, loops=0):
+    """
+    This starts a movie playing. 
+    """
+    
+    if renpy.game.less_updates:
+        return
+
+    global default_size
+    
+    if size is not None:
+        default_size = size
+    
+    filename = [ filename ]
+
+    if loops == -1:
+        loop = True
+    else:
+        loop = False
+        filename = filename * (loops + 1)
+
+    renpy.audio.music.play(filename, channel='movie', loop=loop)
+
+movie_start_fullscreen = movie_start
+movie_start_displayable = movie_start
+
+def early_interact():
+    """
+    Called early in the interact process, to clear out the fullscreen
+    flag.
+    """
+
+    global fullscreen
+    global current_movie
+    
+    fullscreen = True
+    current_movie = None
+    
+
+def interact():
+    """
+    This is called each time the screen is redrawn. It helps us decide if
+    the movie should be displayed fullscreen or not.
+    """
+
+    global surface
+    global surface_file
+    
+    if not renpy.audio.music.get_playing("movie"):
+        surface = None
+        surface_file = None
+        return False
+        
+    if fullscreen: 
+        return True
+    else:
+        return False
+
+def get_movie_texture():
+    """
+    Gets a movie texture we can draw to the screen.
+    """
+    
+    global surface
+    global surface_file
+
+    playing = renpy.audio.music.get_playing("movie")
+
+    pss = renpy.audio.audio.pss
+
+    if pss:
+        size = pss.movie_size()
+    else:
+        size = (64, 64)
+
+    if (surface is None) or (surface.get_size() != size) or (surface_file != playing):
+        surface = renpy.display.pgrender.surface(size, False)
+        surface_file = playing
+        surface.fill((0, 0, 0, 255))
+
+    tex = None
+
+    if playing is not None:
+        renpy.display.render.mutated_surface(surface)
+        tex = renpy.display.draw.load_texture(surface, True)
+
+    return tex
+
+
+def render_movie(width, height):
+    tex = get_movie_texture()
+    
+    if tex is None:
+        return None
+    
+    sw, sh = tex.get_size()
+    
+    scale = min(1.0 * width / sw, 1.0 * height / sh)
+    
+    dw = scale * sw
+    dh = scale * sh
+    
+    rv = renpy.display.render.Render(width, height, opaque=True)
+    rv.forward = renpy.display.render.Matrix2D(1.0 / scale, 0.0, 0.0, 1.0 / scale)
+    rv.reverse = renpy.display.render.Matrix2D(scale, 0.0, 0.0, scale)
+    rv.blit(tex, (int((width - dw) / 2), int((height - dh) / 2)))
+
+    return rv
+
+class Movie(renpy.display.core.Displayable):
+    """
+    This is a displayable that shows the current movie.
+    """
+
+    fullscreen = False
+    
+    def __init__(self, fps=24, size=None, **properties):
+        """
+        @param fps: The framerate that the movie should be shown at.
+        """
+        super(Movie, self).__init__(**properties)
+        self.size = size
+        
+    def render(self, width, height, st, at):
+        
+        size = self.size
+        
+        if size is None:
+            size = default_size
+
+        width, height = size
+    
+        rv = render_movie(width, height)
+        
+        if rv is None:
+            rv = renpy.display.render.Render(0, 0)
+
+        # Usually we get redrawn when the frame is ready - but we want
+        # the movie to disappear if it's ended, or if it hasn't started
+        # yet.
+        renpy.display.render.redraw(self, 0.1)
+    
+        return rv
+    
+            
+    def per_interact(self):
+        global fullscreen
+        fullscreen = False
+
+        global current_movie
+        current_movie = self
+        
+            
+def playing():
+    return renpy.audio.music.get_playing("movie")
+
+def frequent():
+    """
+    Called to update the video playback. Returns true if a video refresh is 
+    needed, false otherwise.
+    """
+
+    if not playing():
+        return 0
+
+    pss = renpy.audio.audio.pss
+ 
+    if pss.needs_alloc():
+    
+        if renpy.display.video.fullscreen and renpy.display.draw.fullscreen_surface:
+            surf = renpy.display.draw.fullscreen_surface
+        else:
+            get_movie_texture()
+            surf = renpy.display.scale.real(surface)
+    
+        pss.alloc_event(surf)
+
+    rv = pss.refresh_event()
+
+    if rv and current_movie is not None:
+        renpy.display.render.redraw(current_movie, 0)
+         
+    return rv    
diff --git a/unrpyc/renpy/game.py b/unrpyc/renpy/game.py
new file mode 100644
index 0000000..028da26
--- /dev/null
+++ b/unrpyc/renpy/game.py
@@ -0,0 +1,437 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This module is intended to be used as a singleton object.
+# It's purpose is to store in one global all of the data that would
+# be to annoying to lug around otherwise. 
+
+import renpy.display
+
+# The basepath.
+basepath = None
+
+# A list of paths that we search to load things. This is searched for
+# everything that can be loaded, before archives are used.
+searchpath = [ ]
+
+# The options that were read off the command line.
+args = None
+
+# The game's script.
+script = None
+
+# A stack of execution contexts.
+contexts = [ ]
+
+# The interface that the game uses to interact with the user.
+interface = None
+
+# Are we inside lint?
+lint = False
+
+# The RollbackLog that keeps track of changes to the game state
+# and to the store.
+log = None
+
+# Some useful additional information about program execution that
+# can be added to the exception.
+exception_info = ''
+
+# Used to store style information.
+style = None
+
+# The set of statements we've seen in this session.
+seen_session = { }
+
+# The set of statements we've ever seen.
+seen_ever = { }
+
+# True if we're in the first interaction after a rollback or rollforward.
+after_rollback = False
+
+# Code that's run after the init code.
+post_init = [ ]
+
+# Should we attempt to run in a mode that uses less memory?
+less_memory = False
+
+# Should we attempt to run in a mode that minimizes the number
+# of screen updates?
+less_updates = False
+
+# Should we never show the mouse?
+less_mouse = False
+
+# Should we not imagedissiolve?
+less_imagedissolve = False
+
+# The class that's used to hold the persistent data.
+class Persistent(object):
+
+    def __setstate__(self, data):
+        vars(self).update(data)
+
+    def __getstate__(self):
+        return vars(self)
+
+    # Undefined attributes return None.
+    def __getattr__(self, attr):
+        return None
+        
+# The persistent data that's kept from session to session
+persistent = Persistent()
+
+class Preferences(renpy.object.Object):
+    """
+    Stores preferences that will one day be persisted.
+    """
+    __version__ = 5
+
+    def after_upgrade(self, version):
+        if version < 1:
+            self.mute_volumes = 0
+        if version < 2:
+            self.using_afm_enable = False
+        if version < 3:
+            self.physical_size = None
+        if version < 4:
+            self.renderer = "auto"
+            self.performance_test = True
+        if version < 5:
+            self.language = None
+            
+    def __init__(self):
+        self.fullscreen = False 
+        self.skip_unseen = False
+        self.text_cps = 0
+        self.afm_time = 0
+        self.afm_enable = True
+        
+        # These will be going away soon.
+        self.sound = True
+        self.music = True
+
+        # 2 - All transitions.
+        # 1 - Only non-default transitions.
+        # 0 - No transitions.
+        self.transitions = 2
+
+        self.skip_after_choices = False
+
+        # Mixer channel info.
+
+        # A map from channel name to the current volume (between 0 and 1).
+        self.volumes = { }
+
+        # True if the channel should not play music. False
+        # otherwise. (Not used anymore.)
+        self.mute = { }
+
+        # Joystick mappings.
+        self.joymap = dict(
+            joy_left="Axis 0.0 Negative",
+            joy_right="Axis 0.0 Positive",
+            joy_up="Axis 0.1 Negative",
+            joy_down="Axis 0.1 Positive",
+            joy_dismiss="Button 0.0")
+        
+        # The size of the window, or None if we don't know it yet.
+        self.physical_size = None
+        
+        # The graphics renderer we use.
+        self.renderer = "auto"
+        
+        # Should we do a performance test on startup?
+        self.performance_test = True
+
+        # The language we use for translations.
+        self.language = None
+        
+    def set_volume(self, mixer, volume):
+        self.volumes[mixer] = volume
+
+    def get_volume(self, mixer):
+        return self.volumes.get(mixer, 0)
+        
+    def set_mute(self, mixer, mute):
+        self.mute[mixer] = mute
+
+    def get_mute(self, mixer):
+        return self.mute[mixer]
+    
+# The current preferences.
+preferences = Preferences()
+
+class RestartContext(Exception):
+    """
+    Restarts the current context. If `label` is given, calls that label
+    in the restarted context.
+    """
+
+    def __init__(self, label):
+        self.label = label
+
+class RestartTopContext(Exception):
+    """
+    Restarts the top context. If `label` is given, calls that label
+    in the restarted context.
+    """
+
+    def __init__(self, label):
+        self.label = label
+   
+class FullRestartException(Exception):
+    """
+    An exception of this type forces a hard restart, completely
+    destroying the store and config and so on.
+    """
+
+    def __init__(self, reason="end_game"): # W0231
+        self.reason = reason
+
+class UtterRestartException(Exception):
+    """
+    An exception of this type forces an even harder restart, causing
+    Ren'Py and the script to be reloaded.
+    """
+
+class QuitException(Exception):
+    """
+    An exception of this class will let us force a safe quit, from
+    anywhere in the program.
+    
+    `relaunch`
+        If given, the program will run another copy of itself, with the
+        same arguments.
+    """
+
+    def __init__(self, relaunch=False):
+        Exception.__init__(self)
+        self.relaunch = relaunch
+
+class JumpException(Exception):
+    """
+    This should be raised with a label as the only argument. This causes
+    the current statement to terminate, and execution to be transferred
+    to the named label.
+    """
+
+class JumpOutException(Exception):
+    """
+    This should be raised with a label as the only argument. This exits
+    the current context, and then raises a JumpException.
+    """
+
+class CallException(Exception):
+    """
+    Raise this exception to cause the current statement to terminate, 
+    and control to be transferred to the named label.
+    """
+
+    def __init__(self, label, args, kwargs):
+        Exception.__init__(self)
+        
+        self.label = label
+        self.args = args
+        self.kwargs = kwargs
+
+class EndReplay(Exception):
+    """
+    Raise this exception to end the current replay (the current call to 
+    call_replay).
+    """
+
+class ParseErrorException(Exception):
+    """
+    This is raised when a parse error occurs, after it has been
+    reported to the user.
+    """
+
+# A tuple of exceptions that should not be caught by the 
+# exception reporting mechanism.
+CONTROL_EXCEPTIONS = (
+    RestartContext,
+    RestartTopContext,
+    FullRestartException,
+    UtterRestartException,
+    QuitException,
+    JumpException,
+    JumpOutException,
+    CallException,
+    EndReplay,
+    ParseErrorException,
+    KeyboardInterrupt,
+    )
+
+    
+def context(index=-1):
+    """
+    Return the current execution context, or the context at the
+    given index if one is specified.
+    """
+
+    return contexts[index]
+
+def invoke_in_new_context(callable, *args, **kwargs): #@ReservedAssignment
+    """
+    This pushes the current context, and invokes the given python
+    function in a new context. When that function returns or raises an
+    exception, it removes the new context, and restores the current
+    context.
+
+    Additional arguments and keyword arguments are passed to the
+    callable.
+
+    Please note that the context so created cannot execute renpy
+    code. So exceptions that change the flow of renpy code (like
+    the one created by renpy.jump) cause this context to terminate,
+    and are handled by the next higher context.
+
+    If you want to execute renpy code from the function, you can call
+    it with renpy.call_in_new_context.
+
+    Use this to begin a second interaction with the user while
+    inside an interaction.
+    """
+
+    context = renpy.execution.Context(False, contexts[-1], clear=True)
+    contexts.append(context)
+
+    if renpy.display.interface is not None:
+        renpy.display.interface.enter_context()
+
+    try:
+
+        return callable(*args, **kwargs)
+
+    except renpy.game.JumpOutException as e:        
+
+        raise renpy.game.JumpException(e.args[0])
+
+    finally:
+
+        contexts.pop()
+        contexts[-1].do_deferred_rollback()
+
+        if interface.restart_interaction and contexts:
+            contexts[-1].scene_lists.focused = None
+
+        
+        
+def call_in_new_context(label, *args, **kwargs):
+    """
+    This code creates a new context, and starts executing code from
+    that label in the new context. Rollback is disabled in the
+    new context. (Actually, it will just bring you back to the
+    real context.)
+
+    Use this to begin a second interaction with the user while
+    inside an interaction.
+    """
+
+    context = renpy.execution.Context(False, contexts[-1], clear=True)
+    contexts.append(context)
+
+    if renpy.display.interface is not None:
+        renpy.display.interface.enter_context()
+    
+    if args:
+        renpy.store._args = args
+    else:
+        renpy.store._args = None
+
+    if kwargs:    
+        renpy.store._kwargs = renpy.python.RevertableDict(kwargs)
+    else:
+        renpy.store._kwargs = None
+    
+    try:
+            
+        context.goto_label(label)
+        renpy.execution.run_context(False)
+
+        rv = renpy.store._return #@UndefinedVariable
+
+        return rv
+        
+    except renpy.game.JumpOutException as e:        
+
+        raise renpy.game.JumpException(e.args[0])
+
+    finally:
+
+        contexts.pop()
+        contexts[-1].do_deferred_rollback()
+   
+        if interface.restart_interaction and contexts:
+            contexts[-1].scene_lists.focused = None
+
+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()
+
+    for k, v in scope.items():
+        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:
+        contexts.pop()
+        renpy.game.log = old_log
+        sb.restore()
+         
+        if interface.restart_interaction and contexts:
+            contexts[-1].scene_lists.focused = None
+    
+    
+# Type information.       
+if False:
+    script = renpy.script.Script()
+    interface = renpy.display.core.Interface()
+    log = renpy.python.RollbackLog()
diff --git a/unrpyc/renpy/log.py b/unrpyc/renpy/log.py
new file mode 100644
index 0000000..37fec9a
--- /dev/null
+++ b/unrpyc/renpy/log.py
@@ -0,0 +1,152 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+# This module handles the logging of messages to a file.
+
+import os.path
+import codecs
+import traceback
+import platform
+import time
+import tempfile
+
+import renpy
+
+# The file events are logged to.
+log_file = None
+
+class LogFile(object):
+    """
+    This manages one of our logfiles.
+    """
+    
+    def __init__(self, name, append=False, developer=False):
+        """
+        `name`
+            The name of the logfile, without the .txt extension.
+        `append`
+            If true, we will append to the logfile. If false, we will truncate
+            it to an empty file the first time we write to it.
+        `developer`
+            If true, nothing happens if config.developer is not set to True.
+        """
+        
+        self.name = name
+        self.append = append
+        self.developer = developer
+        self.file = None
+        
+        # File-like attributes.
+        self.softspace = 0
+        self.newlines = None
+        
+        # Should we emulate file's write method? We do so if this is True.
+        self.raw_write = False
+        
+    def open(self): #@ReservedAssignment
+
+        if self.file:
+            return True
+
+        if self.developer and not renpy.config.developer:
+            return False
+        
+        if not renpy.config.log_enable:
+            return False
+
+        try:
+            base = os.environ.get("RENPY_LOG_BASE", renpy.config.basedir)
+            fn = os.path.join(base, self.name + ".txt")
+        
+            altfn = os.path.join(tempfile.gettempdir(), "renpy-" + self.name + ".txt")
+            
+        
+            if renpy.android:
+                print("Logging to", fn)
+
+            if self.append:
+                mode = "a"
+            else:
+                mode = "w"
+
+            try:        
+                self.file = codecs.open(fn, mode, "utf-8")
+            except:
+                self.file = codecs.open(altfn, mode, "utf-8")
+
+            if self.append:                
+                self.write('')
+                self.write('=' * 78)
+                self.write('')
+
+            self.write("%s", time.ctime())
+            self.write("%s", platform.platform())
+            self.write("%s", renpy.version)
+            self.write("%s %s", renpy.config.name, renpy.config.version)
+            self.write("")
+            
+            return True
+
+        except:
+            return False
+
+    def write(self, s, *args):
+        """
+        Formats `s` with args, and writes it to the logfile.
+        """
+
+        if self.open():
+        
+            if not self.raw_write:
+                s = s % args
+                s += "\n"
+
+            if not isinstance(s, str):
+                s = s.decode("latin-1")
+
+            s = s.replace("\n", "\r\n")            
+            
+            self.file.write(s)
+            self.file.flush()
+            
+    def exception(self):
+        """
+        Writes the exception to the logfile.
+        """
+        
+        self.raw_write = True
+        traceback.print_exc(None, self)
+        self.raw_write = False
+
+# A map from the log name to a log object.
+log_cache = { }
+
+def open(name, append=False, developer=False): #@ReservedAssignment
+    rv = log_cache.get(name, None)
+    
+    if rv is None:
+        rv = LogFile(name, append=append, developer=developer)
+        log_cache[name] = rv
+        
+    return rv
+
+    
+    
diff --git a/unrpyc/renpy/object.py b/unrpyc/renpy/object.py
new file mode 100644
index 0000000..c595e8c
--- /dev/null
+++ b/unrpyc/renpy/object.py
@@ -0,0 +1,60 @@
+# Copyright 2004-2013 Tom Rothamel <pytom@bishoujo.us>
+#
+# Permission is hereby granted, free of charge, to any person
+# obtaining a copy of this software and associated documentation files
+# (the "Software"), to deal in the Software without restriction,
+# including without limitation the rights to use, copy, modify, merge,
+# publish, distribute, sublicense, and/or sell copies of the Software,
+# and to permit persons to whom the Software is furnished to do so,
+# subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be
+# included in all copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+# EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+# MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+# NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+# LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+# OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+# WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+class Object(object):
+    """
+    Our own base class. Contains methods to simplify serialization.
+    """
+
+    __version__ = 0
+    
+    nosave = [ ]
+
+    def __getstate__(self):
+        rv = vars(self).copy()
+        
+        for f in self.nosave:
+            if f in rv:
+                del rv[f]
+
+
+        rv["__version__"] = self.__version__
+                
+        return rv
+
+
+    # None, to prevent this from being called when unnecessary.
+    after_setstate = None
+
+    def __setstate__(self, new_dict):
+
+        version = new_dict.pop("__version__", 0)
+        
+        self.__dict__.update(new_dict)
+        
+        if version != self.__version__:
+            self.after_upgrade(version) # E1101
+            
+        if self.after_setstate:
+            self.after_setstate() # E1102
+
+# We don't handle slots with this mechanism, since the call to vars should
+# throw an error.
-- 
cgit v1.2.3-70-g09d2