summaryrefslogtreecommitdiff
path: root/pcr/renpy-python3
diff options
context:
space:
mode:
Diffstat (limited to 'pcr/renpy-python3')
-rw-r--r--pcr/renpy-python3/PKGBUILD82
-rw-r--r--pcr/renpy-python3/python3.patch5100
-rw-r--r--pcr/renpy-python3/renpy3
-rw-r--r--pcr/renpy-python3/renpy-ffmpeg30.patch94
-rw-r--r--pcr/renpy-python3/renpy.desktop9
-rw-r--r--pcr/renpy-python3/renpy.pngbin0 -> 18422 bytes
6 files changed, 5288 insertions, 0 deletions
diff --git a/pcr/renpy-python3/PKGBUILD b/pcr/renpy-python3/PKGBUILD
new file mode 100644
index 000000000..55ae76867
--- /dev/null
+++ b/pcr/renpy-python3/PKGBUILD
@@ -0,0 +1,82 @@
+# $Id: PKGBUILD 161858 2016-02-16 17:48:18Z alucryd $
+# Maintainer (Arch): Maxime Gauduin <alucryd@archlinux.org>
+# Contributor (Arch): Cravix <dr.neemous@gmail.com>
+# Contributor (Arch): AlexanderR <rvacheva@nxt.ru>
+# Contributor (Arch): zhn <zhangn1985@gmail.com>
+# Maintainer: Márcio Silva <coadde@parabola.nu>
+
+pkgbase=renpy
+pkgname=('renpy-python3' 'renpy-python3-demos')
+pkgver=6.99.8
+pkgrel=3
+pkgdesc="The Ren'Py Visual Novel Engine, with Python 3 support"
+arch=('i686' 'x86_64' 'armv7h')
+url='http://www.renpy.org'
+license=('MIT')
+depends=('ffmpeg' 'glew' 'python-pygame-sdl2')
+makedepends=('cython')
+source=("http://www.renpy.org/dl/${pkgver}/renpy-${pkgver}-source.tar.bz2"
+ 'renpy'
+ 'renpy.desktop'
+ 'renpy.png'
+ 'renpy-ffmpeg30.patch'
+ 'python3.patch')
+sha256sums=('0eb0c763bf7e977db06039c69751f1ed5e69c4b738f7f6d975e99e8729eff58e'
+ '993046143826c74f15ad3990d662878952594545eb315e3f1857ffe32e62399b'
+ 'fccde3461617a098a78d938d9db782d403eda410a84ab52825a597498ab95834'
+ '611edc07a40ccb8e04e8858847fc1d2a066d29c2ed54e5b357880a0605818dc5'
+ 'c2d27a3f6b74f874a790ce6c12e9d4b718784478d8a8aa23c879d186f60a25ab'
+ 'd3fa4d78ceb52f4ec26bc18e87f94e19bcf579766ef2ffee080d11194f0fb913')
+
+prepare() {
+ cd renpy-${pkgver}-source
+
+ patch -Np1 -i ../renpy-ffmpeg30.patch
+ patch -Np1 -i ../python3.patch # use "2to3" to convert all .py file to Python 3
+}
+
+build() {
+ cd renpy-${pkgver}-source
+
+ export RENPY_CYTHON='cython'
+
+ python module/setup.py build
+}
+
+package_renpy-python3() {
+ optdepends=('renpy-demos: Tutorial and The Question demos'
+ 'tk: Set projects directory')
+ conflicts=('renpy')
+
+ cd renpy-${pkgver}-source
+
+ python module/setup.py install --root="${pkgdir}" --prefix='/usr' --optimize='1'
+
+ install -dm 755 "${pkgdir}"/usr/{bin,share/{applications,pixmaps,renpy,doc}}
+
+ cp -dr --no-preserve='ownership' doc launcher renpy renpy.py templates "${pkgdir}"/usr/share/renpy/
+ ln -s /usr/share/renpy/doc "${pkgdir}"/usr/share/doc/renpy
+
+ install -m 755 ../renpy "${pkgdir}"/usr/bin/
+ install -m 644 ../renpy.desktop "${pkgdir}"/usr/share/applications/
+ install -m 644 ../renpy.png "${pkgdir}"/usr/share/pixmaps/
+
+ install -dm 755 "${pkgdir}"/usr/share/licenses/renpy
+ install -m 644 LICENSE.txt "${pkgdir}"/usr/share/licenses/renpy/
+}
+
+package_renpy-python3-demos() {
+ depends=('renpy-python3')
+ conflicts=('renpy-python3-demos')
+
+ cd renpy-${pkgver}-source
+
+ install -dm 755 "${pkgdir}"/usr/share/renpy
+
+ cp -dr --no-preserve='ownership' the_question tutorial "${pkgdir}"/usr/share/renpy/
+
+ install -dm 755 "${pkgdir}"/usr/share/licenses
+ ln -s renpy "${pkgdir}"/usr/share/licenses/renpy-demos
+}
+
+# vim: ts=2 sw=2 et:
diff --git a/pcr/renpy-python3/python3.patch b/pcr/renpy-python3/python3.patch
new file mode 100644
index 000000000..972242311
--- /dev/null
+++ b/pcr/renpy-python3/python3.patch
@@ -0,0 +1,5100 @@
+diff --git a/launcher/game/EasyDialogsWin.py b/launcher/game/EasyDialogsWin.py
+index aea17ed..f63c883 100644
+--- a/launcher/game/EasyDialogsWin.py
++++ b/launcher/game/EasyDialogsWin.py
+@@ -19,8 +19,6 @@ This module uses DLOG resources 260 and on.
+ Based upon STDWIN dialogs with the same names and functions.
+ """
+
+-from __future__ import division
+-
+ import os
+
+ import ctypes
+@@ -606,7 +604,7 @@ def AskFileForOpen(
+ ofn.lpstrTitle = windowTitle
+ ofn.lpstrInitialDir = defaultLocation
+
+- if typeList and filter(None, typeList):
++ if typeList and [_f for _f in typeList if _f]:
+ lpstrFilter = ''
+ for typeSpec in typeList:
+ try:
+@@ -672,10 +670,10 @@ def AskFileForOpen(
+ ofn.lpfnHook = LPOFNHOOKPROC(hookProc)
+
+ if fn(ctypes.byref(ofn)):
+- filenames = filter(None, filename.split('\0'))
++ filenames = [_f for _f in filename.split('\0') if _f]
+ if len(filenames) > 1:
+ dir, filenames = filenames[0], filenames[1:]
+- return map(lambda fn: os.path.join(dir, fn), filenames)
++ return [os.path.join(dir, fn) for fn in filenames]
+ elif multiple:
+ return filenames
+ else:
+@@ -771,7 +769,7 @@ def AskFolder(
+ def BrowseCallback(hwnd, uMsg, lParam, lpData):
+ if uMsg == BFFM_INITIALIZED:
+ if actionButtonLabel:
+- label = unicode(actionButtonLabel, errors='replace')
++ label = str(actionButtonLabel, errors='replace')
+ user32.SendMessageW(hwnd, BFFM_SETOKTEXT, 0, label)
+ if cancelButtonLabel:
+ cancelButton = user32.GetDlgItem(hwnd, IDCANCEL)
+@@ -995,14 +993,14 @@ def GetArgv(optionlist=None, commandlist=None, addoldfile=1, addnewfile=1, addfo
+ if item[0] == '"':
+ while item[-1] != '"':
+ if not tmplist:
+- raise RuntimeError, "Unterminated quoted argument"
++ raise RuntimeError("Unterminated quoted argument")
+ item = item + ' ' + tmplist[0]
+ del tmplist[0]
+ item = item[1:-1]
+ if item[0] == "'":
+ while item[-1] != "'":
+ if not tmplist:
+- raise RuntimeError, "Unterminated quoted argument"
++ raise RuntimeError("Unterminated quoted argument")
+ item = item + ' ' + tmplist[0]
+ del tmplist[0]
+ item = item[1:-1]
+@@ -1029,7 +1027,7 @@ def test():
+ argv = GetArgv(optionlist=optionlist, commandlist=commandlist, addoldfile=0)
+ Message("Command line: %s"%' '.join(argv))
+ for i in range(len(argv)):
+- print 'arg[%d] = %r' % (i, argv[i])
++ print('arg[%d] = %r' % (i, argv[i]))
+ ok = AskYesNoCancel("Do you want to proceed?")
+ ok = AskYesNoCancel("Do you want to identify?", yes="Identify", no="No")
+ if ok > 0:
+@@ -1053,11 +1051,11 @@ def test():
+ try:
+ if hasattr(MacOS, 'SchedParams'):
+ appsw = MacOS.SchedParams(1, 0)
+- for i in xrange(20):
++ for i in range(20):
+ bar.inc()
+ time.sleep(0.05)
+ bar.set(0,100)
+- for i in xrange(100):
++ for i in range(100):
+ bar.set(i)
+ time.sleep(0.05)
+ if i % 10 == 0:
+diff --git a/launcher/game/change_icon.py b/launcher/game/change_icon.py
+index 900cff6..23caaab 100644
+--- a/launcher/game/change_icon.py
++++ b/launcher/game/change_icon.py
+@@ -62,9 +62,9 @@ class BinFile(object):
+ def name(self):
+ c = self.u16()
+
+- rv = u""
++ rv = ""
+ for _i in range(c):
+- rv += unichr(self.u16())
++ rv += chr(self.u16())
+
+ return rv
+
+@@ -143,11 +143,11 @@ def parse_directory(bf, offset):
+ def show_resources(d, prefix):
+
+ if not isinstance(d, dict):
+- print prefix, "Codepage", d[0], "length", len(d[1])
++ print(prefix, "Codepage", d[0], "length", len(d[1]))
+ return
+
+ for k in d:
+- print prefix, k
++ print(prefix, k)
+ show_resources(d[k], prefix + " ")
+
+ ##############################################################################
+@@ -201,8 +201,8 @@ class Packer(object):
+ return rv
+
+ def pack_dict(self, d, offset):
+- name_entries = sorted((a, b) for a, b in d.iteritems() if isinstance(a, unicode))
+- id_entries = sorted((a, b) for a, b in d.iteritems() if isinstance(a, int))
++ name_entries = sorted((a, b) for a, b in d.items() if isinstance(a, str))
++ id_entries = sorted((a, b) for a, b in d.items() if isinstance(a, int))
+
+ rv = struct.pack("<IIHHHH", 0, 0, 4, 0, len(name_entries), len(id_entries))
+
+@@ -211,7 +211,7 @@ class Packer(object):
+ rest = ""
+
+ for (name, value) in name_entries + id_entries:
+- if isinstance(name, unicode):
++ if isinstance(name, str):
+ name = 0x80000000 | self.pack_name(name)
+
+ if isinstance(value, dict):
+@@ -230,7 +230,7 @@ class Packer(object):
+ # This loads in an icon file, and returns a dictionary that is suitable for
+ # use in the resources of an exe file.
+ def load_icon(fn):
+- f = BinFile(file(fn, "rb").read())
++ f = BinFile(open(fn, "rb").read())
+
+ f.seek(0)
+ f.u16()
+@@ -291,7 +291,7 @@ def change_icons(oldexe, icofn):
+ physize = rsrc_section.SizeOfRawData
+ virsize = rsrc_section.Misc_VirtualSize
+
+- f = file(oldexe, "rb")
++ f = open(oldexe, "rb")
+ f.seek(base)
+ data = f.read(physize)
+ f.close()
+@@ -343,7 +343,7 @@ def change_icons(oldexe, icofn):
+
+ if __name__ == "__main__":
+
+- f = file(sys.argv[3], "wb")
++ f = open(sys.argv[3], "wb")
+ f.write(change_icons(sys.argv[1], sys.argv[2]))
+ f.close()
+
+diff --git a/launcher/game/pefile.py b/launcher/game/pefile.py
+index 9fd10a5..5e20cd3 100644
+--- a/launcher/game/pefile.py
++++ b/launcher/game/pefile.py
+@@ -83,8 +83,8 @@ IMAGE_OS2_SIGNATURE_LE = 0x454C
+ IMAGE_VXD_SIGNATURE = 0x454C
+ IMAGE_NT_SIGNATURE = 0x00004550
+ IMAGE_NUMBEROF_DIRECTORY_ENTRIES= 16
+-IMAGE_ORDINAL_FLAG = 0x80000000L
+-IMAGE_ORDINAL_FLAG64 = 0x8000000000000000L
++IMAGE_ORDINAL_FLAG = 0x80000000
++IMAGE_ORDINAL_FLAG64 = 0x8000000000000000
+ OPTIONAL_HEADER_MAGIC_PE = 0x10b
+ OPTIONAL_HEADER_MAGIC_PE_PLUS = 0x20b
+
+@@ -167,7 +167,7 @@ section_characteristics = [
+ ('IMAGE_SCN_MEM_SHARED', 0x10000000),
+ ('IMAGE_SCN_MEM_EXECUTE', 0x20000000),
+ ('IMAGE_SCN_MEM_READ', 0x40000000),
+- ('IMAGE_SCN_MEM_WRITE', 0x80000000L) ]
++ ('IMAGE_SCN_MEM_WRITE', 0x80000000) ]
+
+ SECTION_CHARACTERISTICS = dict([(e[1], e[0]) for e in
+ section_characteristics]+section_characteristics)
+@@ -574,7 +574,7 @@ class UnicodeStringWrapperPostProcessor:
+
+ try:
+ data = self.pe.get_data(self.rva_ptr, 2)
+- except PEFormatError, e:
++ except PEFormatError as e:
+ return False
+
+ if len(data)<2:
+@@ -646,7 +646,7 @@ class Dump:
+ The text can be indented with the optional argument 'indent'.
+ """
+
+- if isinstance(txt, unicode):
++ if isinstance(txt, str):
+ try:
+ txt = str(txt)
+ except UnicodeEncodeError:
+@@ -768,7 +768,7 @@ class Structure:
+ self.__all_zeroes__ = True
+
+ self.__unpacked_data_elms__ = struct.unpack(self.__format__, data)
+- for i in xrange(len(self.__unpacked_data_elms__)):
++ for i in range(len(self.__unpacked_data_elms__)):
+ for key in self.__keys__[i]:
+ #self.values[key] = self.__unpacked_data_elms__[i]
+ setattr(self, key, self.__unpacked_data_elms__[i])
+@@ -778,7 +778,7 @@ class Structure:
+
+ new_values = []
+
+- for i in xrange(len(self.__unpacked_data_elms__)):
++ for i in range(len(self.__unpacked_data_elms__)):
+
+ for key in self.__keys__[i]:
+ new_val = getattr(self, key)
+@@ -814,15 +814,15 @@ class Structure:
+ for key in keys:
+
+ val = getattr(self, key)
+- if isinstance(val, int) or isinstance(val, long):
++ if isinstance(val, int):
+ val_str = '0x%-8X' % (val)
+ if key == 'TimeDateStamp' or key == 'dwTimeStamp':
+ try:
+ val_str += ' [%s UTC]' % time.asctime(time.gmtime(val))
+- except exceptions.ValueError, e:
++ except exceptions.ValueError as e:
+ val_str += ' [INVALID TIME]'
+ else:
+- val_str = ''.join(filter(lambda c:c != '\0', str(val)))
++ val_str = ''.join([c for c in str(val) if c != '\0'])
+
+ dump.append('%-30s %s' % (key+':', val_str))
+
+@@ -957,7 +957,7 @@ class DataContainer:
+ """Generic data container."""
+
+ def __init__(self, **args):
+- for key, value in args.items():
++ for key, value in list(args.items()):
+ setattr(self, key, value)
+
+
+@@ -1371,7 +1371,7 @@ class PE:
+
+ try:
+ structure.__unpack__(data)
+- except PEFormatError, err:
++ except PEFormatError as err:
+ self.__warnings.append(
+ 'Corrupt header "%s" at file offset %d. Exception: %s' % (
+ format[0], file_offset, str(err)) )
+@@ -1390,7 +1390,7 @@ class PE:
+ """
+
+ if fname:
+- fd = file(fname, 'rb')
++ fd = open(fname, 'rb')
+ self.__data__ = fd.read()
+ fd.close()
+ elif data:
+@@ -1555,7 +1555,7 @@ class PE:
+ 'Normal values are never larger than 0x10, the value is: 0x%x' %
+ self.OPTIONAL_HEADER.NumberOfRvaAndSizes )
+
+- for i in xrange(int(0x7fffffffL & self.OPTIONAL_HEADER.NumberOfRvaAndSizes)):
++ for i in range(int(0x7fffffff & self.OPTIONAL_HEADER.NumberOfRvaAndSizes)):
+
+ if len(self.__data__[offset:]) == 0:
+ break
+@@ -1668,7 +1668,7 @@ class PE:
+ """
+
+ for warning in self.__warnings:
+- print '>', warning
++ print('>', warning)
+
+
+ def full_load(self):
+@@ -1705,7 +1705,7 @@ class PE:
+ for entry in self.FileInfo:
+ if hasattr(entry, 'StringTable'):
+ for st_entry in entry.StringTable:
+- for key, entry in st_entry.entries.items():
++ for key, entry in list(st_entry.entries.items()):
+
+ offsets = st_entry.entries_offsets[key]
+ lengths = st_entry.entries_lengths[key]
+@@ -1738,12 +1738,12 @@ class PE:
+ file_data[
+ offsets[1] + len(entry)*2 :
+ offsets[1] + lengths[1]*2 ] = [
+- u'\0' ] * remainder*2
++ '\0' ] * remainder*2
+
+ new_file_data = ''.join( [ chr(ord(c)) for c in file_data] )
+
+ if filename:
+- f = file(filename, 'wb+')
++ f = open(filename, 'wb+')
+ f.write(new_file_data)
+ f.close()
+ else:
+@@ -1767,7 +1767,7 @@ class PE:
+
+ self.sections = []
+
+- for i in xrange(self.FILE_HEADER.NumberOfSections):
++ for i in range(self.FILE_HEADER.NumberOfSections):
+ section = SectionStructure(self.__IMAGE_SECTION_HEADER_format__)
+ if not section:
+ break
+@@ -1846,7 +1846,7 @@ class PE:
+ matching the filter "flag_filter".
+ """
+
+- return [(f[0], f[1]) for f in flag_dict.items() if
++ return [(f[0], f[1]) for f in list(flag_dict.items()) if
+ isinstance(f[0], str) and f[0].startswith(flag_filter)]
+
+
+@@ -1956,7 +1956,7 @@ class PE:
+ rva += bnd_descr.sizeof()
+
+ forwarder_refs = []
+- for idx in xrange(bnd_descr.NumberOfModuleForwarderRefs):
++ for idx in range(bnd_descr.NumberOfModuleForwarderRefs):
+ # Both structures IMAGE_BOUND_IMPORT_DESCRIPTOR and
+ # IMAGE_BOUND_FORWARDER_REF have the same size.
+ bnd_frwd_ref = self.__unpack_data__(
+@@ -2092,7 +2092,7 @@ class PE:
+ data = self.get_data(data_rva, size)
+
+ entries = []
+- for idx in xrange(len(data)/2):
++ for idx in range(len(data)/2):
+ word = struct.unpack('<H', data[idx*2:(idx+1)*2])[0]
+ reloc_type = (word>>12)
+ reloc_offset = (word&0x0fff)
+@@ -2110,10 +2110,10 @@ class PE:
+ dbg_size = Structure(self.__IMAGE_DEBUG_DIRECTORY_format__).sizeof()
+
+ debug = []
+- for idx in xrange(size/dbg_size):
++ for idx in range(size/dbg_size):
+ try:
+ data = self.get_data(rva+dbg_size*idx, dbg_size)
+- except PEFormatError, e:
++ except PEFormatError as e:
+ self.__warnings.append(
+ 'Invalid debug information. Can\'t read ' +
+ 'data at RVA: 0x%x' % rva)
+@@ -2167,7 +2167,7 @@ class PE:
+ # If the RVA is invalid all would blow up. Some EXEs seem to be
+ # specially nasty and have an invalid RVA.
+ data = self.get_data(rva, Structure(self.__IMAGE_RESOURCE_DIRECTORY_format__).sizeof() )
+- except PEFormatError, e:
++ except PEFormatError as e:
+ self.__warnings.append(
+ 'Invalid resources directory. Can\'t read ' +
+ 'directory data at RVA: 0x%x' % rva)
+@@ -2201,7 +2201,7 @@ class PE:
+
+ strings_to_postprocess = list()
+
+- for idx in xrange(number_of_entries):
++ for idx in range(number_of_entries):
+
+ res = self.parse_resource_entry(rva)
+ if res is None:
+@@ -2227,7 +2227,7 @@ class PE:
+ entry_name = UnicodeStringWrapperPostProcessor(self, ustr_offset)
+ strings_to_postprocess.append(entry_name)
+
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the resources directory, ' +
+ 'attempting to read entry name. ' +
+@@ -2328,7 +2328,7 @@ class PE:
+ # If the RVA is invalid all would blow up. Some EXEs seem to be
+ # specially nasty and have an invalid RVA.
+ data = self.get_data(rva, Structure(self.__IMAGE_RESOURCE_DATA_ENTRY_format__).sizeof() )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing a resource directory data entry, ' +
+ 'the RVA is invalid: 0x%x' % ( rva ) )
+@@ -2353,13 +2353,13 @@ class PE:
+ return None
+
+ #resource.NameIsString = (resource.Name & 0x80000000L) >> 31
+- resource.NameOffset = resource.Name & 0x7FFFFFFFL
++ resource.NameOffset = resource.Name & 0x7FFFFFFF
+
+- resource.__pad = resource.Name & 0xFFFF0000L
+- resource.Id = resource.Name & 0x0000FFFFL
++ resource.__pad = resource.Name & 0xFFFF0000
++ resource.Id = resource.Name & 0x0000FFFF
+
+- resource.DataIsDirectory = (resource.OffsetToData & 0x80000000L) >> 31
+- resource.OffsetToDirectory = resource.OffsetToData & 0x7FFFFFFFL
++ resource.DataIsDirectory = (resource.OffsetToData & 0x80000000) >> 31
++ resource.OffsetToDirectory = resource.OffsetToData & 0x7FFFFFFF
+
+ return resource
+
+@@ -2407,7 +2407,7 @@ class PE:
+ ustr_offset = version_struct.OffsetToData + versioninfo_struct.sizeof()
+ try:
+ versioninfo_string = self.get_string_u_at_rva( ustr_offset )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the version information, ' +
+ 'attempting to read VS_VERSION_INFO string. Can\'t ' +
+@@ -2418,7 +2418,7 @@ class PE:
+
+ # If the structure does not contain the expected name, it's assumed to be invalid
+ #
+- if versioninfo_string != u'VS_VERSION_INFO':
++ if versioninfo_string != 'VS_VERSION_INFO':
+
+ self.__warnings.append('Invalid VS_VERSION_INFO block')
+ return
+@@ -2487,7 +2487,7 @@ class PE:
+ stringfileinfo_offset + versioninfo_struct.sizeof() )
+ try:
+ stringfileinfo_string = self.get_string_u_at_rva( ustr_offset )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the version information, ' +
+ 'attempting to read StringFileInfo string. Can\'t ' +
+@@ -2506,7 +2506,7 @@ class PE:
+
+ # Parse a StringFileInfo entry
+ #
+- if stringfileinfo_string.startswith(u'StringFileInfo'):
++ if stringfileinfo_string.startswith('StringFileInfo'):
+
+ if stringfileinfo_struct.Type == 1 and stringfileinfo_struct.ValueLength == 0:
+
+@@ -2533,7 +2533,7 @@ class PE:
+ stringtable_struct.sizeof() )
+ try:
+ stringtable_string = self.get_string_u_at_rva( ustr_offset )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the version information, ' +
+ 'attempting to read StringTable string. Can\'t ' +
+@@ -2568,7 +2568,7 @@ class PE:
+ try:
+ key = self.get_string_u_at_rva( ustr_offset )
+ key_offset = self.get_offset_from_rva( ustr_offset )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the version information, ' +
+ 'attempting to read StringTable Key string. Can\'t ' +
+@@ -2584,7 +2584,7 @@ class PE:
+ value = self.get_string_u_at_rva( ustr_offset,
+ max_length = string_struct.ValueLength )
+ value_offset = self.get_offset_from_rva( ustr_offset )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the version information, ' +
+ 'attempting to read StringTable Value string. ' +
+@@ -2629,7 +2629,7 @@ class PE:
+
+ # Parse a VarFileInfo entry
+ #
+- elif stringfileinfo_string.startswith( u'VarFileInfo' ):
++ elif stringfileinfo_string.startswith( 'VarFileInfo' ):
+
+ varfileinfo_struct = stringfileinfo_struct
+ varfileinfo_struct.name = 'VarFileInfo'
+@@ -2659,7 +2659,7 @@ class PE:
+ var_struct.sizeof() )
+ try:
+ var_string = self.get_string_u_at_rva( ustr_offset )
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the version information, ' +
+ 'attempting to read VarFileInfo Var string. ' +
+@@ -2681,7 +2681,7 @@ class PE:
+ raw_data[varword_offset+2:varword_offset+4], 0)
+ varword_offset += 4
+
+- if isinstance(word1, (int, long)) and isinstance(word1, (int, long)):
++ if isinstance(word1, int) and isinstance(word1, int):
+ var_struct.entry = {var_string: '0x%04x 0x%04x' % (word1, word2)}
+
+ var_offset = self.dword_align(
+@@ -2750,7 +2750,7 @@ class PE:
+
+ exports = []
+
+- for i in xrange(export_dir.NumberOfNames):
++ for i in range(export_dir.NumberOfNames):
+
+
+ symbol_name = self.get_string_at_rva(
+@@ -2787,7 +2787,7 @@ class PE:
+
+ ordinals = [exp.ordinal for exp in exports]
+
+- for idx in xrange(export_dir.NumberOfFunctions):
++ for idx in range(export_dir.NumberOfFunctions):
+
+ if not idx+export_dir.Base in ordinals:
+ symbol_address = self.get_dword_from_data(
+@@ -2828,7 +2828,7 @@ class PE:
+ # If the RVA is invalid all would blow up. Some PEs seem to be
+ # specially nasty and have an invalid RVA.
+ data = self.get_data( rva, Structure(self.__IMAGE_DELAY_IMPORT_DESCRIPTOR_format__).sizeof() )
+- except PEFormatError, e:
++ except PEFormatError as e:
+ self.__warnings.append(
+ 'Error parsing the Delay import directory at RVA: 0x%x' % ( rva ) )
+ break
+@@ -2850,7 +2850,7 @@ class PE:
+ import_desc.pINT,
+ import_desc.pIAT,
+ None)
+- except PEFormatError, e:
++ except PEFormatError as e:
+ self.__warnings.append(
+ 'Error parsing the Delay import directory. ' +
+ 'Invalid import data at RVA: 0x%x' % ( rva ) )
+@@ -2881,7 +2881,7 @@ class PE:
+ # If the RVA is invalid all would blow up. Some EXEs seem to be
+ # specially nasty and have an invalid RVA.
+ data = self.get_data(rva, Structure(self.__IMAGE_IMPORT_DESCRIPTOR_format__).sizeof() )
+- except PEFormatError, e:
++ except PEFormatError as e:
+ self.__warnings.append(
+ 'Error parsing the Import directory at RVA: 0x%x' % ( rva ) )
+ break
+@@ -2901,7 +2901,7 @@ class PE:
+ import_desc.OriginalFirstThunk,
+ import_desc.FirstThunk,
+ import_desc.ForwarderChain)
+- except PEFormatError, excp:
++ except PEFormatError as excp:
+ self.__warnings.append(
+ 'Error parsing the Import directory. ' +
+ 'Invalid Import data at RVA: 0x%x' % ( rva ) )
+@@ -2934,7 +2934,7 @@ class PE:
+ imported_symbols = []
+ imports_section = self.get_section_by_rva(first_thunk)
+ if not imports_section:
+- raise PEFormatError, 'Invalid/corrupt imports.'
++ raise PEFormatError('Invalid/corrupt imports.')
+
+
+ # Import Lookup Table. Contains ordinals or pointers to strings.
+@@ -2961,7 +2961,7 @@ class PE:
+ return None
+
+
+- for idx in xrange(len(table)):
++ for idx in range(len(table)):
+
+ imp_ord = None
+ imp_hint = None
+@@ -2989,7 +2989,7 @@ class PE:
+ # Get the Hint
+ imp_hint = self.get_word_from_data(data, 0)
+ imp_name = self.get_string_at_rva(table[idx].AddressOfData+2)
+- except PEFormatError, e:
++ except PEFormatError as e:
+ pass
+
+ imp_address = first_thunk+self.OPTIONAL_HEADER.ImageBase+idx*4
+@@ -3030,7 +3030,7 @@ class PE:
+
+ try:
+ data = self.get_data( rva, Structure(format).sizeof() )
+- except PEFormatError, e:
++ except PEFormatError as e:
+ self.__warnings.append(
+ 'Error parsing the import table. ' +
+ 'Invalid data at RVA: 0x%x' % ( rva ) )
+@@ -3135,7 +3135,7 @@ class PE:
+ end = None
+ return self.header[rva:end]
+
+- raise PEFormatError, 'data at RVA can\'t be fetched. Corrupt header?'
++ raise PEFormatError('data at RVA can\'t be fetched. Corrupt header?')
+
+ return s.get_data(rva, length)
+
+@@ -3158,7 +3158,7 @@ class PE:
+ s = self.get_section_by_rva(rva)
+ if not s:
+
+- raise PEFormatError, 'data at RVA can\'t be fetched. Corrupt header?'
++ raise PEFormatError('data at RVA can\'t be fetched. Corrupt header?')
+
+ return s.get_offset_from_rva(rva)
+
+@@ -3205,21 +3205,21 @@ class PE:
+ # If the RVA is invalid all would blow up. Some EXEs seem to be
+ # specially nasty and have an invalid RVA.
+ self.get_data(rva, 2)
+- except PEFormatError, e:
++ except PEFormatError as e:
+ return None
+
+ #length = struct.unpack('<H', data)[0]
+
+- s = u''
+- for idx in xrange(max_length):
++ s = ''
++ for idx in range(max_length):
+ try:
+ uchr = struct.unpack('<H', self.get_data(rva+2*idx, 2))[0]
+ except struct.error:
+ break
+
+- if unichr(uchr) == u'\0':
++ if chr(uchr) == '\0':
+ break
+- s += unichr(uchr)
++ s += chr(uchr)
+
+ return s
+
+@@ -3251,7 +3251,7 @@ class PE:
+
+ def print_info(self):
+ """Print all the PE header information in a human readable from."""
+- print self.dump_info()
++ print(self.dump_info())
+
+
+ def dump_info(self, dump=None):
+@@ -3334,7 +3334,7 @@ class PE:
+ hasattr(self.OPTIONAL_HEADER, 'DATA_DIRECTORY') ):
+
+ dump.add_header('Directories')
+- for idx in xrange(len(self.OPTIONAL_HEADER.DATA_DIRECTORY)):
++ for idx in range(len(self.OPTIONAL_HEADER.DATA_DIRECTORY)):
+ directory = self.OPTIONAL_HEADER.DATA_DIRECTORY[idx]
+ dump.add_lines(directory.dump())
+ dump.add_newline()
+@@ -3368,7 +3368,7 @@ class PE:
+ [dump.add_line(' '+line) for line in st_entry.dump()]
+ dump.add_line(' LangID: '+st_entry.LangID)
+ dump.add_newline()
+- for str_entry in st_entry.entries.items():
++ for str_entry in list(st_entry.entries.items()):
+ dump.add_line( ' ' +
+ convert_to_printable(str_entry[0]) + ': ' +
+ convert_to_printable(str_entry[1]) )
+@@ -3380,8 +3380,8 @@ class PE:
+ [dump.add_line(' '+line) for line in var_entry.dump()]
+ dump.add_line(
+ ' ' +
+- convert_to_printable(var_entry.entry.keys()[0]) +
+- ': ' + var_entry.entry.values()[0])
++ convert_to_printable(list(var_entry.entry.keys())[0]) +
++ ': ' + list(var_entry.entry.values())[0])
+
+ dump.add_newline()
+
+@@ -3930,5 +3930,5 @@ class PE:
+ if __name__ == "__main__":
+ import sys
+ pe = PE(sys.argv[1])
+- print pe
++ print(pe)
+
+diff --git a/launcher/game/project.rpy b/launcher/game/project.rpy
+index 8ff9ade..524e838 100644
+--- a/launcher/game/project.rpy
++++ b/launcher/game/project.rpy
+@@ -273,7 +273,7 @@ init python in project:
+
+ for f in files:
+
+- data = file(self.unelide_filename(f))
++ data = open(self.unelide_filename(f))
+
+ for l, line in enumerate(data):
+ l += 1
+diff --git a/launcher/game/tkaskdir.py b/launcher/game/tkaskdir.py
+index 5b64008..a88d089 100644
+--- a/launcher/game/tkaskdir.py
++++ b/launcher/game/tkaskdir.py
+@@ -31,8 +31,8 @@ try:
+ from tkinter import Tk
+ from tkinter.filedialog import askdirectory
+ except ImportError:
+- from Tkinter import Tk
+- from tkFileDialog import askdirectory
++ from tkinter import Tk
++ from tkinter.filedialog import askdirectory
+
+ # Binary mode stdout for python3.
+ try:
+diff --git a/module/generate_linebreak.py b/module/generate_linebreak.py
+index 670adb7..8914199 100644
+--- a/module/generate_linebreak.py
++++ b/module/generate_linebreak.py
+@@ -41,24 +41,24 @@ other_classes = " PITCH AI BK CB CJ CR LF NL SA SG SP XX"
+
+ lines = breaking.split("\n")
+
+-print "# This is generated code. Do not edit."
+-print
++print("# This is generated code. Do not edit.")
++print()
+
+ # A map from character class to the number that represents it.
+ cl = { }
+
+
+ for i, j in enumerate((lines[0] + other_classes).split()):
+- print "cdef char BC_{} = {}".format(j, i)
++ print("cdef char BC_{} = {}".format(j, i))
+ cl[j] = i
+
+-print "CLASSES = {"
++print("CLASSES = {")
+
+ for i, j in enumerate((lines[0] + other_classes).split()):
+- print " \"{}\" : {},".format(j, i)
++ print(" \"{}\" : {},".format(j, i))
+ cl[j] = i
+
+-print "}"
++print("}")
+
+ rules = [ ]
+
+@@ -66,12 +66,12 @@ for l in lines[1:]:
+ for c in l.split()[1:]:
+ rules.append(c)
+
+-print
+-print "cdef char *break_rules = \"" + "".join(rules) + "\""
++print()
++print("cdef char *break_rules = \"" + "".join(rules) + "\"")
+
+ cc = [ 'XX' ] * 65536
+
+-for l in file("LineBreak.txt"):
++for l in open("LineBreak.txt"):
+ m = re.match("(\w+)\.\.(\w+);(\w\w)", l)
+ if m:
+ start = int(m.group(1), 16)
+@@ -108,7 +108,7 @@ def generate(name, func):
+ assert "CJ" not in ncc
+ assert "AI" not in ncc
+
+- print "cdef char *break_" + name + " = \"" + "".join("\\x%02x" % cl[i] for i in ncc) + "\""
++ print("cdef char *break_" + name + " = \"" + "".join("\\x%02x" % cl[i] for i in ncc) + "\"")
+
+ def western(i, cl):
+ if cl == "CJ":
+diff --git a/module/generate_styles.py b/module/generate_styles.py
+index e8f6957..f2942ca 100644
+--- a/module/generate_styles.py
++++ b/module/generate_styles.py
+@@ -19,17 +19,13 @@
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-from __future__ import print_function, unicode_literals, division, absolute_import
+-
+-str = unicode # @ReservedAssignment
+-
+ import collections
+ import os
+
+ try:
+ from io import StringIO
+ except:
+- from StringIO import StringIO
++ from io import StringIO
+
+ # Paths
+ BASE = os.path.dirname(os.path.abspath(__file__))
+@@ -335,7 +331,7 @@ synthetic_properties = sorted_dict(
+
+ all_properties = collections.OrderedDict()
+
+-for k, v in style_properties.items():
++for k, v in list(style_properties.items()):
+ all_properties[k] = [ (k, None) ]
+
+ all_properties.update(synthetic_properties)
+@@ -372,7 +368,7 @@ class CodeGen(object):
+ return
+
+ with open(self.filename, "wb") as f:
+- f.write(text)
++ f.write(bytes(text, "utf-8"))
+
+ def write(self, s, *args, **kwargs):
+ out = " " * self.depth
+@@ -403,7 +399,7 @@ def generate_constants():
+ g.write("DEF PREFIX_COUNT = {}", PREFIX_COUNT)
+ g.write("DEF STYLE_PROPERTY_COUNT = {}", style_property_count)
+
+- for p in prefixes.values():
++ for p in list(prefixes.values()):
+ if p.index < 0:
+ continue
+
+@@ -460,13 +456,13 @@ def generate_property_functions():
+ This generates code that defines the property functions.
+ """
+
+- for prefix in sorted(prefixes.values(), key=lambda p : p.index):
++ for prefix in sorted(list(prefixes.values()), key=lambda p : p.index):
+ g = CodeGen("module/gen/style_{}functions.pyx".format(prefix.name))
+
+ g.write('include "style_common.pxi"')
+ g.write('')
+
+- for propname, proplist in all_properties.items():
++ for propname, proplist in list(all_properties.items()):
+ generate_property_function(g, prefix, propname, proplist)
+
+ g.close()
+@@ -522,13 +518,13 @@ def generate_sets():
+
+ ap = collections.OrderedDict()
+
+- for k, v in all_properties.items():
++ for k, v in list(all_properties.items()):
+ ap[k] = [ i[0] for i in v ]
+
+ prefix_priority = collections.OrderedDict()
+ prefix_alts = collections.OrderedDict()
+
+- for p in prefixes.values():
++ for p in list(prefixes.values()):
+ prefix_priority[p.name] = p.priority
+ prefix_alts[p.name] = p.alt_names
+
+diff --git a/module/maketegl.py b/module/maketegl.py
+index b5e1bda..4b9edc6 100644
+--- a/module/maketegl.py
++++ b/module/maketegl.py
+@@ -26,8 +26,6 @@
+ # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ # SUCH DAMAGE.
+
+-from __future__ import print_function
+-
+ # Modified in 2010,2014 by PyTom to generate Cython code that uses glew.
+
+ VERSION = "0.1"
+diff --git a/module/pysdlsound/__init__.py b/module/pysdlsound/__init__.py
+index 8f2c8e9..a349a0d 100644
+--- a/module/pysdlsound/__init__.py
++++ b/module/pysdlsound/__init__.py
+@@ -9,7 +9,7 @@ except:
+ pass
+
+ try:
+- import linmixer #@UnresolvedImport
++ from . import linmixer #@UnresolvedImport
+ sys.modules['linmixer'] = sys.modules['pysdlsound.linmixer']
+ except:
+ pass
+diff --git a/module/setup.py b/module/setup.py
+index 38854d0..ea390e2 100644
+--- a/module/setup.py
++++ b/module/setup.py
+@@ -103,7 +103,7 @@ if has_fribidi and (not android) and (not ios):
+ try:
+ # Some versions of fribidi require glib, and it doesn't hurt to include it in
+ # our path.
+- glib_flags = subprocess.check_output(["pkg-config", "--cflags", "glib-2.0"])
++ glib_flags = subprocess.check_output(["pkg-config", "--cflags", "glib-2.0"]).decode("utf-8")
+ setuplib.extra_compile_args.extend(glib_flags.split())
+ except:
+ pass
+@@ -232,4 +232,4 @@ import renpy
+ setuplib.setup("Ren'Py", renpy.version[7:])
+
+ if not has_fribidi:
+- print "Warning: Did not include fribidi."
++ print("Warning: Did not include fribidi.")
+diff --git a/module/setuplib.py b/module/setuplib.py
+index 3b27819..c2b6eee 100644
+--- a/module/setuplib.py
++++ b/module/setuplib.py
+@@ -96,9 +96,9 @@ def include(header, directory=None, optional=True):
+ return False
+
+ if directory is None:
+- print "Could not find required header {0}.".format(header)
++ print("Could not find required header {0}.".format(header))
+ else:
+- print "Could not find required header {0}/{1}.".format(directory, header)
++ print("Could not find required header {0}/{1}.".format(directory, header))
+
+ sys.exit(-1)
+
+@@ -133,7 +133,7 @@ def library(name, optional=False):
+ if optional:
+ return False
+
+- print "Could not find required library {0}.".format(name)
++ print("Could not find required library {0}.".format(name))
+ sys.exit(-1)
+
+ # A list of extension objects that we use.
+@@ -182,7 +182,7 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[], pyx=None
+ elif os.path.exists(fn):
+ pass
+ else:
+- print "Could not find {0}.".format(fn)
++ print("Could not find {0}.".format(fn))
+ sys.exit(-1)
+
+ module_dir = os.path.dirname(fn)
+@@ -190,7 +190,7 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[], pyx=None
+ # Figure out what it depends on.
+ deps = [ fn ]
+
+- f = file(fn)
++ f = open(fn)
+ for l in f:
+
+ m = re.search(r'from\s*([\w.]+)\s*cimport', l)
+@@ -243,19 +243,19 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[], pyx=None
+ elif os.path.exists(dep_fn):
+ pass
+ else:
+- print "{0} depends on {1}, which can't be found.".format(fn, dep_fn)
++ print("{0} depends on {1}, which can't be found.".format(fn, dep_fn))
+ sys.exit(-1)
+
+ if os.path.getmtime(dep_fn) > c_mtime:
+ out_of_date = True
+
+ if out_of_date and not cython_command:
+- print "WARNING:", name, "is out of date, but RENPY_CYTHON isn't set."
++ print("WARNING:", name, "is out of date, but RENPY_CYTHON isn't set.")
+ out_of_date = False
+
+ # If the file is out of date, regenerate it.
+ if out_of_date:
+- print name, "is out of date."
++ print(name, "is out of date.")
+
+ try:
+ import subprocess
+@@ -280,10 +280,10 @@ def cython(name, source=[], libs=[], compile_if=True, define_macros=[], pyx=None
+ "-o",
+ c_fn])
+
+- except subprocess.CalledProcessError, e:
+- print
+- print str(e)
+- print
++ except subprocess.CalledProcessError as e:
++ print()
++ print(str(e))
++ print()
+ sys.exit(-1)
+
+ # Build the module normally once we have the c file.
+@@ -299,7 +299,7 @@ def find_unnecessary_gen():
+ if i in necessary_gen:
+ continue
+
+- print "Unnecessary file", os.path.join("gen", i)
++ print("Unnecessary file", os.path.join("gen", i))
+
+
+ py_modules = [ ]
+@@ -326,14 +326,14 @@ def copyfile(source, dest, replace=None, replace_with=None):
+ if os.path.getmtime(sfn) <= os.path.getmtime(dfn):
+ return
+
+- sf = file(sfn, "rb")
++ sf = open(sfn, "rb")
+ data = sf.read()
+ sf.close()
+
+ if replace:
+ data = data.replace(replace, replace_with)
+
+- df = file(dfn, "wb")
++ df = open(dfn, "wb")
+ df.write("# This file was automatically generated from " + source + "\n")
+ df.write("# Modifications will be automatically overwritten.\n\n")
+ df.write(data)
+diff --git a/renpy/__init__.py b/renpy/__init__.py
+index e6f5310..91a6f24 100644
+--- a/renpy/__init__.py
++++ b/renpy/__init__.py
+@@ -27,7 +27,7 @@ import os
+ import copy
+ import types
+ import threading
+-import cPickle
++import pickle
+
+ ################################################################################
+ # Version information
+@@ -200,14 +200,14 @@ class Backup():
+ if mobile:
+ return
+
+- for m in sys.modules.values():
++ for m in list(sys.modules.values()):
+ if m is None:
+ continue
+
+ self.backup_module(m)
+
+ # A pickled version of self.objects.
+- self.objects_pickle = cPickle.dumps(self.objects, cPickle.HIGHEST_PROTOCOL)
++ self.objects_pickle = pickle.dumps(self.objects, pickle.HIGHEST_PROTOCOL)
+
+ self.objects = None
+
+@@ -229,7 +229,7 @@ class Backup():
+
+ self.names[mod] = set(vars(mod).keys())
+
+- for k, v in vars(mod).iteritems():
++ for k, v in vars(mod).items():
+
+ if k.startswith("__") and k.endswith("__"):
+ continue
+@@ -248,10 +248,10 @@ class Backup():
+ # If we have a problem pickling things, uncomment the next block.
+
+ try:
+- cPickle.dumps(v, cPickle.HIGHEST_PROTOCOL)
++ pickle.dumps(v, pickle.HIGHEST_PROTOCOL)
+ except:
+- print "Cannot pickle", name + "." + k, "=", repr(v)
+- print "Reduce Ex is:", repr(v.__reduce_ex__(cPickle.HIGHEST_PROTOCOL))
++ print("Cannot pickle", name + "." + k, "=", repr(v))
++ print("Reduce Ex is:", repr(v.__reduce_ex__(pickle.HIGHEST_PROTOCOL)))
+
+ def restore(self):
+ """
+@@ -263,15 +263,15 @@ class Backup():
+ return
+
+ # Remove new variables from the module.
+- for mod, names in self.names.iteritems():
++ for mod, names in self.names.items():
+ modvars = vars(mod)
+ for name in set(modvars.keys()) - names:
+ del modvars[name]
+
+
+- objects = cPickle.loads(self.objects_pickle)
++ objects = pickle.loads(self.objects_pickle)
+
+- for k, v in self.variables.iteritems():
++ for k, v in self.variables.items():
+ mod, field = k
+ setattr(mod, field, objects[v])
+
+@@ -473,12 +473,12 @@ def post_import():
+ import subprocess
+ sys.modules['renpy.subprocess'] = subprocess
+
+- for k, v in renpy.defaultstore.__dict__.iteritems():
++ 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().iteritems():
++ for k, v in globals().items():
+ vars(renpy.exports).setdefault(k, v)
+
+
+@@ -519,7 +519,7 @@ def reload_all():
+ renpy.display.interface = None
+
+ # Delete the store modules.
+- for i in sys.modules.keys():
++ for i in list(sys.modules.keys()):
+ if i.startswith("store") or i == "renpy.store":
+ m = sys.modules[i]
+
+diff --git a/renpy/add_from.py b/renpy/add_from.py
+index 9935b29..5b2e3b5 100644
+--- a/renpy/add_from.py
++++ b/renpy/add_from.py
+@@ -82,7 +82,7 @@ def process_file(fn):
+ consumed = 0
+
+ # The output.
+- output = u""
++ output = ""
+
+ for position, target in edits:
+ output += data[consumed:position]
+diff --git a/renpy/ast.py b/renpy/ast.py
+index ec2cbf5..78499e2 100644
+--- a/renpy/ast.py
++++ b/renpy/ast.py
+@@ -31,6 +31,7 @@ import renpy.display
+ import re
+ import time
+ import md5
++import collections
+
+ def statement_name(name):
+ """
+@@ -102,7 +103,7 @@ class ParameterInfo(object):
+
+ extrapos = tuple(args[len(self.positional):])
+
+- for name, value in kwargs.iteritems():
++ for name, value in kwargs.items():
+ if name in values:
+ if not ignore_errors:
+ raise Exception("Parameter %s has two values." % name)
+@@ -133,7 +134,7 @@ class ParameterInfo(object):
+ if self.extrakw:
+ rv[self.extrakw] = values
+ elif values and not ignore_errors:
+- raise Exception("Unknown keyword arguments: %s" % ( ", ".join(values.keys())))
++ raise Exception("Unknown keyword arguments: %s" % ( ", ".join(list(values.keys()))))
+
+ return rv
+
+@@ -192,7 +193,7 @@ def __newobj__(cls, *args):
+ return cls.__new__(cls, *args)
+
+ # This represents a string containing python code.
+-class PyExpr(unicode):
++class PyExpr(str):
+
+ __slots__ = [
+ 'filename',
+@@ -200,14 +201,14 @@ class PyExpr(unicode):
+ ]
+
+ def __new__(cls, s, filename, linenumber):
+- self = unicode.__new__(cls, s)
++ self = str.__new__(cls, s)
+ self.filename = filename
+ self.linenumber = linenumber
+
+ return self
+
+ def __getnewargs__(self):
+- return (unicode(self), self.filename, self.linenumber) # E1101
++ return (str(self), self.filename, self.linenumber) # E1101
+
+ class PyCode(object):
+
+@@ -291,7 +292,7 @@ class Scry(object):
+ def __getattr__(self, name):
+ return None
+
+- def next(self): #@ReservedAssignment
++ def __next__(self): #@ReservedAssignment
+ if self._next is None:
+ return None
+ else:
+@@ -382,7 +383,7 @@ class Node(object):
+ node.
+ """
+
+- if self.next is old:
++ if self.__next__ is old:
+ self.next = new
+
+ def execute(self):
+@@ -409,8 +410,8 @@ class Node(object):
+ renpy.display.predict.screen to be called as necessary.
+ """
+
+- if self.next:
+- return [ self.next ]
++ if self.__next__:
++ return [ self.__next__ ]
+ else:
+ return [ ]
+
+@@ -421,7 +422,7 @@ class Node(object):
+ """
+
+ rv = Scry()
+- rv._next = self.next # W0201
++ rv._next = self.__next__ # W0201
+ return rv
+
+ def restructure(self, callback):
+@@ -573,7 +574,7 @@ class Say(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("say")
+
+ try:
+@@ -584,8 +585,8 @@ class Say(Node):
+
+ if not (
+ (who is None) or
+- callable(who) or
+- isinstance(who, basestring) ):
++ isinstance(who, collections.Callable) or
++ isinstance(who, str) ):
+
+ raise Exception("Sayer %s is not a function or string." % self.who.encode("utf-8"))
+
+@@ -630,7 +631,7 @@ class Say(Node):
+ finally:
+ renpy.game.context().say_attributes = old_attributes
+
+- return [ self.next ]
++ return [ self.__next__ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+@@ -685,7 +686,7 @@ class Init(Node):
+ chain_block(self.block, None)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("init")
+
+ def restructure(self, callback):
+@@ -743,14 +744,14 @@ class Label(Node):
+ self.next = next
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("label")
+
+ renpy.game.context().mark_seen()
+
+ values = apply_arguments(self.parameters, renpy.store._args, renpy.store._kwargs)
+
+- for k, v in values.iteritems():
++ for k, v in values.items():
+ renpy.exports.dynamic(k)
+ setattr(renpy.store, k, v)
+
+@@ -798,7 +799,7 @@ class Python(Node):
+ renpy.python.create_store(self.store)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("python")
+
+ try:
+@@ -845,7 +846,7 @@ class EarlyPython(Node):
+ return (EarlyPython, self.code.source)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("python early")
+
+ def early_execute(self):
+@@ -889,7 +890,7 @@ class Image(Node):
+ # 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)
++ next_node(self.__next__)
+ statement_name("image")
+
+ if self.code is not None:
+@@ -933,7 +934,7 @@ class Transform(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("transform")
+
+ parameters = getattr(self, "parameters", None)
+@@ -1058,14 +1059,14 @@ class Show(Node):
+ return (Show, tuple(self.imspec[0]))
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("show")
+
+ show_imspec(self.imspec, atl=getattr(self, "atl", None))
+
+ def predict(self):
+ predict_imspec(self.imspec, atl=getattr(self, "atl", None))
+- return [ self.next ]
++ return [ self.__next__ ]
+
+ def analyze(self):
+ if getattr(self, 'atl', None) is not None:
+@@ -1091,7 +1092,7 @@ class ShowLayer(Node):
+ return (ShowLayer, self.layer)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("show layer")
+
+ at_list = [ renpy.python.py_eval(i) for i in self.at_list ]
+@@ -1103,7 +1104,7 @@ class ShowLayer(Node):
+ renpy.exports.layer_at_list(at_list, layer=self.layer)
+
+ def predict(self):
+- return [ self.next ]
++ return [ self.__next__ ]
+
+ def analyze(self):
+ if self.atl is not None:
+@@ -1142,7 +1143,7 @@ class Scene(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("scene")
+
+ renpy.config.scene(self.layer)
+@@ -1155,7 +1156,7 @@ class Scene(Node):
+ if self.imspec:
+ predict_imspec(self.imspec, atl=getattr(self, "atl", None), scene=True)
+
+- return [ self.next ]
++ return [ self.__next__ ]
+
+ def analyze(self):
+ if getattr(self, 'atl', None) is not None:
+@@ -1204,11 +1205,11 @@ class Hide(Node):
+
+ renpy.game.context().images.predict_hide(tag, layer)
+
+- return [ self.next ]
++ return [ self.__next__ ]
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("hide")
+
+ if len(self.imspec) == 3:
+@@ -1252,7 +1253,7 @@ class With(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("with")
+
+ trans = renpy.python.py_eval(self.expr)
+@@ -1276,7 +1277,7 @@ class With(Node):
+ pass
+
+
+- return [ self.next ]
++ return [ self.__next__ ]
+
+
+ class Call(Node):
+@@ -1423,7 +1424,7 @@ class Menu(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("menu")
+
+ choices = [ ]
+@@ -1452,7 +1453,7 @@ class Menu(Node):
+ if choice is not None:
+ next_node(self.items[choice][2][0])
+ else:
+- next_node(self.next)
++ next_node(self.__next__)
+
+
+ def predict(self):
+@@ -1547,7 +1548,7 @@ class Pass(Node):
+ return (Pass,)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("pass")
+
+
+@@ -1585,14 +1586,14 @@ class While(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("while")
+
+ if renpy.python.py_eval(self.condition):
+ next_node(self.block[0])
+
+ def predict(self):
+- return [ self.block[0], self.next ]
++ return [ self.block[0], self.__next__ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+@@ -1640,7 +1641,7 @@ class If(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("if")
+
+ for condition, block in self.entries:
+@@ -1651,7 +1652,7 @@ class If(Node):
+ def predict(self):
+
+ return [ block[0] for _condition, block in self.entries ] + \
+- [ self.next ]
++ [ self.__next__ ]
+
+ def scry(self):
+ rv = Node.scry(self)
+@@ -1726,7 +1727,7 @@ class UserStatement(Node):
+ if rv is not None:
+ return renpy.game.script.lookup(rv)
+ else:
+- return self.next
++ return self.__next__
+
+ def scry(self):
+ rv = Node.scry(self)
+@@ -1787,7 +1788,7 @@ class Define(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("define")
+
+ value = renpy.python.py_eval_bytecode(self.code.bytecode)
+@@ -1834,7 +1835,7 @@ class Default(Node):
+
+ def execute(self):
+
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("default")
+
+ default_statements.append(self)
+@@ -1889,7 +1890,7 @@ class Screen(Node):
+ return (Screen, self.screen.name)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("screen")
+
+ self.screen.define((self.filename, self.linenumber))
+@@ -1956,7 +1957,7 @@ class Translate(Node):
+ statement_name("translate")
+
+ if self.language is not None:
+- next_node(self.next)
++ next_node(self.__next__)
+ raise Exception("Translation nodes cannot be run directly.")
+
+ if self.identifier not in renpy.game.persistent._seen_translates: # @UndefinedVariable
+@@ -2000,7 +2001,7 @@ class EndTranslate(Node):
+ return (EndTranslate,)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("end translate")
+
+ renpy.game.context().translate_identifier = None
+@@ -2030,7 +2031,7 @@ class TranslateString(Node):
+ return (TranslateString,)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("translate string")
+
+ renpy.translation.add_string_translation(self.language, self.old, self.new)
+@@ -2066,7 +2067,7 @@ class TranslatePython(Node):
+ return (TranslatePython, self.code.source)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("translate_python")
+
+ # def early_execute(self):
+@@ -2106,7 +2107,7 @@ class TranslateBlock(Node):
+ chain_block(self.block, None)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("translate_block")
+
+ def restructure(self, callback):
+@@ -2156,7 +2157,7 @@ class Style(Node):
+ return (Style, self.style_name)
+
+ def execute(self):
+- next_node(self.next)
++ next_node(self.__next__)
+ statement_name("style")
+
+ if self.variant is not None:
+@@ -2180,7 +2181,7 @@ class Style(Node):
+
+ if self.properties:
+ properties = { }
+- for name, expr in self.properties.items():
++ for name, expr in list(self.properties.items()):
+ properties[name] = renpy.python.py_eval(expr)
+
+ s.add_properties(properties)
+diff --git a/renpy/atl.py b/renpy/atl.py
+index d0f9d31..582f531 100644
+--- a/renpy/atl.py
++++ b/renpy/atl.py
+@@ -39,7 +39,7 @@ def executing(loc):
+ warpers = { }
+
+ def atl_warper(f):
+- name = f.func_name
++ name = f.__name__
+ warpers[name] = f
+ return f
+
+@@ -135,7 +135,7 @@ def interpolate(t, a, b, type): #@ReservedAssignment
+ 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, basestring)):
++ elif b is None or isinstance(b, (bool, str)):
+ if t >= 1.0:
+ return b
+ else:
+@@ -375,7 +375,7 @@ class ATLTransformBase(renpy.object.Object):
+ raise Exception("Too many arguments passed to ATL transform.")
+
+ # Handle keyword arguments.
+- for k, v in kwargs.iteritems():
++ for k, v in kwargs.items():
+
+ if k in positional:
+ positional.remove(k)
+@@ -1113,7 +1113,7 @@ class Interpolation(Statement):
+ linear, revolution, splines = state
+
+ # Linearly interpolate between the things in linear.
+- for k, (old, new) in linear.iteritems():
++ for k, (old, new) in linear.items():
+ value = interpolate(complete, old, new, PROPERTIES[k])
+
+ setattr(trans.state, k, value)
+@@ -1356,19 +1356,19 @@ class RawOn(RawStatement):
+
+ handlers = { }
+
+- for k, v in self.handlers.iteritems():
++ 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.itervalues():
++ for i in self.handlers.values():
+ i.predict(ctx)
+
+ def mark_constant(self):
+ constant = GLOBAL_CONST
+
+- for block in self.handlers.itervalues():
++ for block in self.handlers.values():
+ block.mark_constant()
+ constant = min(constant, block.constant)
+
+@@ -1450,7 +1450,7 @@ class On(Statement):
+ return "event", (name, arg), None
+
+ def visit(self):
+- return [ j for i in self.handlers.itervalues() for j in i.visit() ]
++ return [ j for i in self.handlers.values() for j in i.visit() ]
+
+
+ # Event statement.
+diff --git a/renpy/audio/androidhw.py b/renpy/audio/androidhw.py
+index 1c3b6ed..e487b56 100644
+--- a/renpy/audio/androidhw.py
++++ b/renpy/audio/androidhw.py
+@@ -79,7 +79,7 @@ class AndroidVideoChannel(object):
+
+ filename = self.queue.pop(0)
+
+- print "Playing", filename
++ print("Playing", filename)
+
+ f = renpy.loader.load(filename)
+
+diff --git a/renpy/audio/audio.py b/renpy/audio/audio.py
+index d265a3a..f322aab 100644
+--- a/renpy/audio/audio.py
++++ b/renpy/audio/audio.py
+@@ -803,7 +803,7 @@ def pause_all():
+ Pause all playback channels.
+ """
+
+- for c in channels.values():
++ for c in list(channels.values()):
+ c.pause()
+
+ def unpause_all():
+@@ -811,5 +811,5 @@ def unpause_all():
+ Unpause all playback channels.
+ """
+
+- for c in channels.values():
++ for c in list(channels.values()):
+ c.unpause()
+diff --git a/renpy/audio/music.py b/renpy/audio/music.py
+index bf959b5..9ccce1c 100644
+--- a/renpy/audio/music.py
++++ b/renpy/audio/music.py
+@@ -76,7 +76,7 @@ def play(filenames, channel="music", loop=None, fadeout=None, synchro_start=Fals
+ if filenames is None:
+ return
+
+- if isinstance(filenames, basestring):
++ if isinstance(filenames, str):
+ filenames = [ filenames ]
+
+ try:
+@@ -151,7 +151,7 @@ def queue(filenames, channel="music", loop=None, clear_queue=True, fadein=0, tig
+ filenames = [ ]
+ loop = False
+
+- if isinstance(filenames, basestring):
++ if isinstance(filenames, str):
+ filenames = [ filenames ]
+
+ try:
+diff --git a/renpy/bootstrap.py b/renpy/bootstrap.py
+index 9dbe2e0..a86088a 100644
+--- a/renpy/bootstrap.py
++++ b/renpy/bootstrap.py
+@@ -64,8 +64,8 @@ def extra_imports():
+ import compiler; compiler
+ import textwrap; textwrap
+ import copy; copy
+- import urllib; urllib
+- import urllib2; urllib2
++ import urllib.request, urllib.parse, urllib.error; urllib
++ import urllib.request, urllib.error, urllib.parse; urllib2
+ import codecs; codecs
+ import rsa; rsa
+ import decimal; decimal
+@@ -102,14 +102,14 @@ trace_local = None
+
+ def trace_function(frame, event, arg):
+ fn = os.path.basename(frame.f_code.co_filename)
+- print >>trace_file, fn, frame.f_lineno, frame.f_code.co_name, event
++ print(fn, frame.f_lineno, frame.f_code.co_name, event, file=trace_file)
+ return trace_local
+
+ def enable_trace(level):
+ global trace_file
+ global trace_local
+
+- trace_file = file("trace.txt", "w", 1)
++ trace_file = open("trace.txt", "w", 1)
+
+ if level > 1:
+ trace_local = trace_function
+@@ -137,13 +137,13 @@ def bootstrap(renpy_base):
+ if os.environ.get("SDL_VIDEODRIVER", "") == "windib":
+ del os.environ["SDL_VIDEODRIVER"]
+
+- renpy_base = unicode(renpy_base, FSENCODING, "replace")
++ renpy_base = str(renpy_base, FSENCODING, "replace")
+
+ # If environment.txt exists, load it into the os.environ dictionary.
+ if os.path.exists(renpy_base + "/environment.txt"):
+ evars = { }
+- execfile(renpy_base + "/environment.txt", evars)
+- for k, v in evars.iteritems():
++ exec(compile(open(renpy_base + "/environment.txt").read(), renpy_base + "/environment.txt", 'exec'), evars)
++ for k, v in evars.items():
+ if k not in os.environ:
+ os.environ[k] = str(v)
+
+@@ -155,8 +155,8 @@ def bootstrap(renpy_base):
+
+ if os.path.exists(alt_path + "/environment.txt"):
+ evars = { }
+- execfile(alt_path + "/environment.txt", evars)
+- for k, v in evars.iteritems():
++ exec(compile(open(alt_path + "/environment.txt").read(), alt_path + "/environment.txt", 'exec'), evars)
++ for k, v in evars.items():
+ if k not in os.environ:
+ os.environ[k] = str(v)
+
+@@ -226,14 +226,14 @@ def bootstrap(renpy_base):
+ import pygame_sdl2
+ pygame_sdl2.import_as_pygame()
+ except:
+- print >>sys.stderr, """\
++ print("""\
+ Could not import pygame_sdl2. Please ensure that this program has been built
+ and unpacked properly. Also, make sure that the directories containing
+ this program do not contain : or ; in their names.
+
+ You may be using a system install of python. Please run {0}.sh,
+ {0}.exe, or {0}.app instead.
+-""".format(name)
++""".format(name), file=sys.stderr)
+
+ raise
+
+@@ -241,13 +241,13 @@ You may be using a system install of python. Please run {0}.sh,
+ try:
+ import _renpy; _renpy
+ except:
+- print >>sys.stderr, """\
++ print("""\
+ Could not import _renpy. Please ensure that this program has been built
+ and unpacked properly.
+
+ You may be using a system install of python. Please run {0}.sh,
+ {0}.exe, or {0}.app instead.
+-""".format(name)
++""".format(name), file=sys.stderr)
+ raise
+
+ # Load up all of Ren'Py, in the right order.
+@@ -276,7 +276,7 @@ You may be using a system install of python. Please run {0}.sh,
+ renpy.config.logdir = basedir
+
+ if not os.path.exists(renpy.config.logdir):
+- os.makedirs(renpy.config.logdir, 0777)
++ os.makedirs(renpy.config.logdir, 0o777)
+
+ renpy.main.main()
+
+@@ -304,7 +304,7 @@ You may be using a system install of python. Please run {0}.sh,
+ except renpy.game.ParseErrorException:
+ pass
+
+- except Exception, e:
++ except Exception as e:
+ renpy.error.report_exception(e)
+ pass
+
+diff --git a/renpy/character.py b/renpy/character.py
+index 2d36bbc..1fc049d 100644
+--- a/renpy/character.py
++++ b/renpy/character.py
+@@ -56,12 +56,12 @@ class DialogueTextTags(object):
+ while True:
+
+ try:
+- self.text += i.next()
++ self.text += next(i)
+
+- quoted = i.next()
+- full_tag = i.next()
+- tag = i.next()
+- value = i.next()
++ quoted = next(i)
++ full_tag = next(i)
++ tag = next(i)
++ value = next(i)
+
+ if value is not None:
+ value = float(value)
+@@ -152,7 +152,7 @@ def compute_widget_properties(who_args, what_args, window_args, variant=None):
+
+ style = d["style"]
+
+- if isinstance(style, basestring):
++ if isinstance(style, str):
+ style = getattr(renpy.store.style, style)
+
+ if variant is not None:
+@@ -230,7 +230,7 @@ def show_display_say(who, what, who_args={}, what_args={}, window_args={},
+
+ def merge_style(style, properties):
+
+- if isinstance(style, basestring):
++ if isinstance(style, str):
+ style = getattr(renpy.store.style, style)
+
+ if variant is not None:
+@@ -764,7 +764,7 @@ class ADVCharacter(object):
+ if not (self.condition is None or renpy.python.py_eval(self.condition)):
+ return True
+
+- if not isinstance(what, basestring):
++ if not isinstance(what, str):
+ raise Exception("Character expects its what argument to be a string, got %r." % (what,))
+
+ self.resolve_say_attributes(False)
+@@ -831,7 +831,7 @@ class ADVCharacter(object):
+ self.do_done(who, what)
+
+ # Finally, log this line of dialogue.
+- if who and isinstance(who, (str, unicode)):
++ if who and isinstance(who, str):
+ renpy.exports.log(who)
+ renpy.exports.log(what)
+ renpy.exports.log("")
+diff --git a/renpy/color.py b/renpy/color.py
+index 4bde2af..8df0c64 100644
+--- a/renpy/color.py
++++ b/renpy/color.py
+@@ -123,7 +123,7 @@ class Color(tuple):
+ if len(c) == 3:
+ return tuple.__new__(cls, c + (int(255 * alpha),))
+
+- if isinstance(c, basestring):
++ if isinstance(c, str):
+ if c[0] == '#':
+ c = c[1:]
+
+@@ -308,7 +308,7 @@ class Color(tuple):
+ `other` may be a string, Color or an HSV tuple.
+ """
+
+- if isinstance(other, basestring):
++ if isinstance(other, str):
+ other = Color(other, alpha=self.alpha)
+ elif not isinstance(other, Color):
+ other = Color(hsv=other, alpha=self.alpha)
+@@ -330,7 +330,7 @@ class Color(tuple):
+ `other` may be a string, Color or an HLS tuple.
+ """
+
+- if isinstance(other, basestring):
++ if isinstance(other, str):
+ other = Color(other, alpha=self.alpha)
+ elif not isinstance(other, Color):
+ other = Color(hls=other, alpha=self.alpha)
+diff --git a/renpy/common/00updater.rpy b/renpy/common/00updater.rpy
+index b18dce0..6a35d66 100644
+--- a/renpy/common/00updater.rpy
++++ b/renpy/common/00updater.rpy
+@@ -93,7 +93,7 @@ init -1500 python in updater:
+ time.sleep(3)
+
+ try:
+- log = file(DEFERRED_UPDATE_LOG, "ab")
++ log = open(DEFERRED_UPDATE_LOG, "ab")
+ except:
+ log = StringIO.StringIO()
+
+@@ -919,7 +919,7 @@ init -1500 python in updater:
+ break
+
+ try:
+- f = file(new_fn + ".part", "rb")
++ f = open(new_fn + ".part", "rb")
+ except:
+ self.log.write("partfile does not exist\n")
+ continue
+@@ -1046,7 +1046,7 @@ init -1500 python in updater:
+ # Extract regular files.
+ tff = tf.extractfile(info)
+ new_path = path + ".new"
+- f = file(new_path, "wb")
++ f = open(new_path, "wb")
+
+ while True:
+ data = tff.read(1024 * 1024)
+diff --git a/renpy/common/_developer/developer.rpym b/renpy/common/_developer/developer.rpym
+index 27922d0..d3377c4 100644
+--- a/renpy/common/_developer/developer.rpym
++++ b/renpy/common/_developer/developer.rpym
+@@ -496,7 +496,7 @@ label _filename_list:
+
+ python hide:
+ import os
+- f = file("files.txt", "w")
++ f = open("files.txt", "w")
+
+ for dirname, dirs, files in os.walk(config.gamedir):
+
+diff --git a/renpy/curry.py b/renpy/curry.py
+index f7266bf..4b47eaa 100644
+--- a/renpy/curry.py
++++ b/renpy/curry.py
+@@ -35,7 +35,7 @@ class Curry(object):
+
+ def __call__(self, *args, **kwargs):
+ return self.callable(*(self.args + args),
+- **dict(self.kwargs.items() + kwargs.items()))
++ **dict(list(self.kwargs.items()) + list(kwargs.items())))
+ def __repr__(self):
+ return "<curry %s %r %r>" % (self.callable, self.args, self.kwargs)
+
+diff --git a/renpy/display/anim.py b/renpy/display/anim.py
+index 70c8fe4..fc31002 100644
+--- a/renpy/display/anim.py
++++ b/renpy/display/anim.py
+@@ -120,7 +120,7 @@ class Edge(object):
+ self.prob = prob
+
+ def add(self, sma):
+- for _i in xrange(0, self.prob):
++ for _i in range(0, self.prob):
+ sma.edges.setdefault(self.old, []).append(self)
+
+
+@@ -201,7 +201,7 @@ class SMAnimation(renpy.display.core.Displayable):
+ self.state = None
+
+ def visit(self):
+- return [ i.image for i in self.states.itervalues() ]
++ return [ i.image for i in self.states.values() ]
+
+ def pick_edge(self, state):
+ """
+@@ -304,10 +304,10 @@ class SMAnimation(renpy.display.core.Displayable):
+
+ args = [ ]
+
+- for state in self.states.itervalues():
++ for state in self.states.values():
+ args.append(state.motion_copy(child))
+
+- for edges in self.edges.itervalues():
++ for edges in self.edges.values():
+ args.extend(edges)
+
+ return SMAnimation(self.initial, delay=self.delay, *args, **self.properties)
+diff --git a/renpy/display/behavior.py b/renpy/display/behavior.py
+index e325abb..7c8849b 100644
+--- a/renpy/display/behavior.py
++++ b/renpy/display/behavior.py
+@@ -30,6 +30,7 @@ from renpy.display.render import render, Render
+ import pygame_sdl2 as pygame
+
+ import math
++import collections
+
+ def compile_event(key, keydown):
+ """
+@@ -395,7 +396,7 @@ class Keymap(renpy.display.layout.Null):
+
+ def event(self, ev, x, y, st):
+
+- for name, action in self.keymap.iteritems():
++ for name, action in self.keymap.items():
+ if map_event(ev, name):
+
+ if self.style.activate_sound:
+@@ -409,7 +410,7 @@ class Keymap(renpy.display.layout.Null):
+ raise renpy.display.core.IgnoreEvent()
+
+ def predict_one_action(self):
+- for i in self.keymap.itervalues():
++ for i in self.keymap.values():
+ predict_action(i)
+
+
+@@ -634,7 +635,7 @@ class Button(renpy.display.layout.Window):
+ predict_action(self.alternate)
+
+ if self.keymap:
+- for v in self.keymap.itervalues():
++ for v in self.keymap.values():
+ predict_action(v)
+
+ def render(self, width, height, st, at):
+@@ -663,7 +664,7 @@ class Button(renpy.display.layout.Window):
+ try:
+ mask = renpy.display.render.render(mask, rv.width, rv.height, st, at)
+ except:
+- if callable(mask):
++ if isinstance(mask, collections.Callable):
+ mask = mask
+ else:
+ raise Exception("Focus_mask must be None, True, a displayable, or a callable.")
+@@ -776,7 +777,7 @@ class Button(renpy.display.layout.Window):
+ return None
+
+ # Check the keymap.
+- for name, action in self.keymap.iteritems():
++ for name, action in self.keymap.items():
+ if map_event(ev, name):
+ return run(action)
+
+@@ -881,7 +882,7 @@ class ImageButton(Button):
+ **properties)
+
+ def visit(self):
+- return self.state_children.values()
++ return list(self.state_children.values())
+
+ def get_child(self):
+ return self.style.child or self.state_children[self.style.prefix]
+@@ -910,8 +911,8 @@ class Input(renpy.text.text.Text): #@UndefinedVariable
+ caret_pos = 0
+ old_caret_pos = 0
+ pixel_width = None
+- default = u""
+- edit_text = u""
++ default = ""
++ edit_text = ""
+
+ def __init__(self,
+ default="",
+@@ -930,7 +931,7 @@ class Input(renpy.text.text.Text): #@UndefinedVariable
+
+ super(Input, self).__init__("", style=style, replaces=replaces, substitute=False, **properties)
+
+- self.default = unicode(default)
++ self.default = str(default)
+ self.content = self.default
+
+ self.length = length
+@@ -1017,7 +1018,7 @@ class Input(renpy.text.text.Text): #@UndefinedVariable
+ def set_content(content):
+
+ if content == "":
+- content = u"\u200b"
++ content = "\u200b"
+
+ if editable:
+ l = len(content)
+@@ -1131,8 +1132,8 @@ class Input(renpy.text.text.Text): #@UndefinedVariable
+ raw_text = ev.text
+
+ elif ev.type == pygame.KEYDOWN:
+- if ev.unicode and ord(ev.unicode[0]) >= 32:
+- raw_text = ev.unicode
++ if ev.str and ord(ev.str[0]) >= 32:
++ raw_text = ev.str
+ elif renpy.display.interface.text_event_in_queue():
+ raw_text = ''
+
+diff --git a/renpy/display/core.py b/renpy/display/core.py
+index 5462336..3b708b8 100644
+--- a/renpy/display/core.py
++++ b/renpy/display/core.py
+@@ -31,7 +31,7 @@ import pygame_sdl2 as pygame
+ import sys
+ import os
+ import time
+-import cStringIO
++import io
+ import threading
+
+ import_time = time.time()
+@@ -276,7 +276,7 @@ class Displayable(renpy.object.Object):
+ return self.__class__.__name__
+
+ def __repr__(self):
+- return "<{} at {:x}>".format(unicode(self).encode("utf-8"), id(self))
++ return "<{} at {:x}>".format(str(self).encode("utf-8"), id(self))
+
+ def find_focusable(self, callback, focus_name):
+
+@@ -963,7 +963,7 @@ class SceneLists(renpy.object.Object):
+ """
+
+ rv = [ ]
+- for l in self.layers.itervalues():
++ for l in self.layers.values():
+ for sle in l:
+ rv.append(sle.displayable)
+
+@@ -976,7 +976,7 @@ class SceneLists(renpy.object.Object):
+ be displayed, or everything will be removed.
+ """
+
+- for i in reversed(xrange(len(self.layers[layer]))):
++ for i in reversed(range(len(self.layers[layer]))):
+
+ sle = self.layers[layer][i]
+
+@@ -1031,7 +1031,7 @@ class SceneLists(renpy.object.Object):
+
+ # Have to iterate in reverse order, since otherwise
+ # the indexes might change.
+- for i in reversed(xrange(len(self.layers[layer]))):
++ for i in reversed(range(len(self.layers[layer]))):
+ self.hide_or_replace(layer, i, hide)
+
+ self.at_list[layer].clear()
+@@ -1047,10 +1047,10 @@ class SceneLists(renpy.object.Object):
+ time with the given time.
+ """
+
+- for l, (t, list) in self.layer_at_list.items(): #@ReservedAssignment
++ 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.iteritems():
++ for l, ll in self.layers.items():
+ self.layers[l] = [ i.update_time(time) for i in ll ]
+
+ def showing(self, layer, name):
+@@ -1590,7 +1590,7 @@ class Interface(object):
+ renpy.display.log.write(s)
+
+ if renpy.android and not renpy.config.log_to_stdout:
+- print s
++ print(s)
+
+ def post_init(self):
+ """
+@@ -1893,7 +1893,7 @@ class Interface(object):
+
+ self.screenshot_surface = surf
+
+- sio = cStringIO.StringIO()
++ sio = io.StringIO()
+ renpy.display.module.save_png(surf, sio, 0)
+ self.screenshot = sio.getvalue()
+ sio.close()
+@@ -2006,7 +2006,7 @@ class Interface(object):
+ scene_lists = renpy.game.context().scene_lists
+
+ # Compute the scene.
+- for layer, d in self.compute_scene(scene_lists).iteritems():
++ for layer, d in self.compute_scene(scene_lists).items():
+ if layer not in self.transition:
+ self.old_scene[layer] = d
+
+@@ -2243,7 +2243,7 @@ class Interface(object):
+
+ renpy.exports.free_memory()
+
+- print "Entered background."
++ print("Entered background.")
+
+ while True:
+ ev = pygame.event.wait()
+@@ -2254,7 +2254,7 @@ class Interface(object):
+ if ev.type == pygame.APP_TERMINATING:
+ sys.exit(0)
+
+- print "Entering foreground."
++ print("Entering foreground.")
+
+ # Since we came back to life, we can get rid of the
+ # auto-reload.
+@@ -2583,7 +2583,7 @@ class Interface(object):
+ renpy.display.tts.set_root(scene[None])
+
+ # If necessary, load all images here.
+- for w in scene.itervalues():
++ for w in scene.values():
+ try:
+ renpy.display.predict.displayable(w)
+ except:
+@@ -2766,7 +2766,7 @@ class Interface(object):
+
+ if first_pass:
+ scene_lists.set_times(self.interact_time)
+- for k, v in self.transition_time.iteritems():
++ for k, v in self.transition_time.items():
+ if v is None:
+ self.transition_time[k] = self.interact_time
+
+@@ -2777,8 +2777,8 @@ class Interface(object):
+ new_time = get_time()
+
+ if self.profile_once or (new_time - self.profile_time > .015):
+- print "Profile: Redraw took %.3f ms." % (1000 * (new_time - self.frame_time))
+- print "Profile: %.3f ms to complete event." % (1000 * (new_time - self.profile_time))
++ print("Profile: Redraw took %.3f ms." % (1000 * (new_time - self.frame_time)))
++ print("Profile: %.3f ms to complete event." % (1000 * (new_time - self.profile_time)))
+
+ self.profile_once = False
+
+diff --git a/renpy/display/dragdrop.py b/renpy/display/dragdrop.py
+index 8816ee0..34aed98 100644
+--- a/renpy/display/dragdrop.py
++++ b/renpy/display/dragdrop.py
+@@ -28,6 +28,7 @@ from renpy.display.core import absolute
+ from renpy.display.behavior import map_event, run, run_unhovered
+
+ import pygame_sdl2 as pygame
++import collections
+
+ def default_drag_group():
+ """
+@@ -449,7 +450,7 @@ class Drag(renpy.display.core.Displayable, renpy.python.RevertableObject):
+ try:
+ mask = renpy.display.render.render(mask, fw, fh, st, at)
+ except:
+- if callable(mask):
++ if isinstance(mask, collections.Callable):
+ mask = mask
+ else:
+ raise Exception("Focus_mask must be None, True, a displayable, or a callable.")
+diff --git a/renpy/display/im.py b/renpy/display/im.py
+index de4a043..52f22d5 100644
+--- a/renpy/display/im.py
++++ b/renpy/display/im.py
+@@ -27,7 +27,7 @@ import renpy.display
+
+ import math
+ import zipfile
+-import cStringIO
++import io
+ import threading
+ import time
+
+@@ -266,7 +266,7 @@ class Cache(object):
+ # 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(self.cache.itervalues(), key=lambda a : a.time):
++ 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
+@@ -375,7 +375,7 @@ class Cache(object):
+
+ # 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 self.pin_cache.keys():
++ for i in list(self.pin_cache.keys()):
+
+ if i in workset:
+ workset.remove(i)
+@@ -509,9 +509,9 @@ class Image(ImageBase):
+
+ def __unicode__(self):
+ if len(self.filename) < 20:
+- return u"Image %r" % self.filename
++ return "Image %r" % self.filename
+ else:
+- return u"Image \u2026%s" % self.filename[-20:]
++ return "Image \u2026%s" % self.filename[-20:]
+
+
+ def get_hash(self):
+@@ -530,7 +530,7 @@ class Image(ImageBase):
+
+ return surf
+
+- except Exception, e:
++ except Exception as e:
+
+ if renpy.config.missing_image_callback:
+ im = renpy.config.missing_image_callback(self.filename)
+@@ -565,7 +565,7 @@ class ZipFileImage(ImageBase):
+ try:
+ zf = zipfile.ZipFile(self.zipfilename, 'r')
+ data = zf.read(self.filename)
+- sio = cStringIO.StringIO(data)
++ sio = io.StringIO(data)
+ rv = renpy.display.pgrender.load_image(sio, self.filename)
+ zf.close()
+ return rv
+@@ -1562,7 +1562,7 @@ def image(arg, loose=False, **properties):
+ if isinstance(arg, ImageBase):
+ return arg
+
+- elif isinstance(arg, basestring):
++ elif isinstance(arg, str):
+ return Image(arg, **properties)
+
+ elif isinstance(arg, renpy.display.image.ImageReference):
+@@ -1608,7 +1608,7 @@ def load_surface(im):
+
+
+ def reset_module():
+- print "Resetting cache."
++ print("Resetting cache.")
+
+ global cache
+ cache = Cache()
+diff --git a/renpy/display/image.py b/renpy/display/image.py
+index e5ae353..64574c3 100644
+--- a/renpy/display/image.py
++++ b/renpy/display/image.py
+@@ -45,7 +45,7 @@ def get_available_image_tags():
+ Returns a list of image tags that have been defined.
+ """
+
+- return [ k for k, v in image_attributes.items() if v ]
++ return [ k for k, v in list(image_attributes.items()) if v ]
+
+ def get_available_image_attributes(tag, attributes=()):
+ """
+@@ -149,7 +149,7 @@ class ImageReference(renpy.display.core.Displayable):
+ self.name = name
+
+ def __unicode__(self):
+- return u"<ImageReference {!r}>".format(self.name)
++ return "<ImageReference {!r}>".format(self.name)
+
+ def __hash__(self):
+ return hash(self.name)
+@@ -215,7 +215,7 @@ class ImageReference(renpy.display.core.Displayable):
+
+ self.param_target = self.target
+
+- except Exception, e:
++ except Exception as e:
+ if renpy.config.debug:
+ raise
+
+@@ -323,7 +323,7 @@ class DynamicImage(renpy.display.core.Displayable):
+ return self.find_target(scope, update)
+
+ def __unicode__(self):
+- return u"DynamicImage {!r}".format(self.name)
++ return "DynamicImage {!r}".format(self.name)
+
+ def __hash__(self):
+ return hash(self.name)
+@@ -514,7 +514,7 @@ class ShownImageInfo(renpy.object.Object):
+ if layer is None:
+ layer = 'master'
+
+- for l, t in self.attributes.keys():
++ for l, t in list(self.attributes.keys()):
+ if l == layer:
+ del self.attributes[l, t]
+
+diff --git a/renpy/display/imagelike.py b/renpy/display/imagelike.py
+index d8b9201..3f9a6b3 100644
+--- a/renpy/display/imagelike.py
++++ b/renpy/display/imagelike.py
+@@ -275,8 +275,8 @@ class Frame(renpy.display.core.Displayable):
+ newcr = Render(cdw, cdh)
+ newcr.clipping = True
+
+- for x in xrange(0, cdw, csw):
+- for y in xrange(0, cdh, csh):
++ for x in range(0, cdw, csw):
++ for y in range(0, cdh, csh):
+ newcr.blit(cr, (x, y))
+
+ cr = newcr
+diff --git a/renpy/display/layout.py b/renpy/display/layout.py
+index 7ded388..3b2fd9f 100644
+--- a/renpy/display/layout.py
++++ b/renpy/display/layout.py
+@@ -180,7 +180,7 @@ class Container(renpy.display.core.Displayable):
+ if len(offsets) != len(children):
+ return None
+
+- for i in xrange(len(offsets) - 1, -1, -1):
++ for i in range(len(offsets) - 1, -1, -1):
+
+ d = children[i]
+ xo, yo = offsets[i]
+@@ -641,9 +641,9 @@ class MultiBox(Container):
+ rv = None
+
+ if self.style.order_reverse:
+- iterator = zip(reversed(self.children), reversed(csts), reversed(cats))
++ iterator = list(zip(reversed(self.children), reversed(csts), reversed(cats)))
+ else:
+- iterator = zip(self.children, csts, cats)
++ iterator = list(zip(self.children, csts, cats))
+
+ for child, cst, cat in iterator:
+
+@@ -889,7 +889,7 @@ class MultiBox(Container):
+
+ def event(self, ev, x, y, st):
+
+- children_offsets = zip(self.children, self.offsets, self.start_times)
++ children_offsets = list(zip(self.children, self.offsets, self.start_times))
+
+ if not self.style.order_reverse:
+ children_offsets.reverse()
+@@ -1126,7 +1126,7 @@ class DynamicDisplayable(renpy.display.core.Displayable):
+ super(DynamicDisplayable, self).__init__()
+ self.child = None
+
+- if isinstance(function, basestring):
++ if isinstance(function, str):
+ args = ( function, )
+ kwargs = { }
+ function = dynamic_displayable_compat
+@@ -1669,7 +1669,7 @@ class Side(Container):
+
+ super(Side, self).__init__(style=style, **properties)
+
+- if isinstance(positions, basestring):
++ if isinstance(positions, str):
+ positions = positions.split()
+
+ seen = set()
+diff --git a/renpy/display/module.py b/renpy/display/module.py
+index 24c0f7d..de7ad91 100644
+--- a/renpy/display/module.py
++++ b/renpy/display/module.py
+@@ -172,11 +172,11 @@ def twomap(src, dst, white, black):
+ wb + 1,
+ wa + 1)
+ else:
+- map(src, dst,
++ list(map(src, dst,
+ ramp(br, wr),
+ ramp(bg, wg),
+ ramp(bb, wb),
+- ramp(0, wa))
++ ramp(0, wa)))
+
+
+ def alpha_munge(src, dst, amap):
+diff --git a/renpy/display/motion.py b/renpy/display/motion.py
+index c80ae9e..d24b2d8 100644
+--- a/renpy/display/motion.py
++++ b/renpy/display/motion.py
+@@ -542,7 +542,7 @@ class Transform(Container):
+ self.arguments = { }
+
+ # Fill self.arguments with a
+- for k, v in kwargs.iteritems():
++ for k, v in kwargs.items():
+
+ prefix = ""
+ prop = k
+@@ -568,7 +568,7 @@ class Transform(Container):
+ prefix = new_prefix
+
+ if "" in self.arguments:
+- for k, v in self.arguments[""].iteritems():
++ for k, v in self.arguments[""].items():
+ setattr(self.state, k, v)
+
+ else:
+@@ -627,7 +627,7 @@ class Transform(Container):
+ if d is None:
+ continue
+
+- for k, v in d.iteritems():
++ for k, v in d.items():
+ setattr(state, k, v)
+
+ return None
+@@ -803,7 +803,7 @@ class Transform(Container):
+ if not offsets:
+ return None
+
+- for i in xrange(len(self.children)-1, -1, -1):
++ for i in range(len(self.children)-1, -1, -1):
+
+ d = children[i]
+ xo, yo = offsets[i]
+@@ -1221,10 +1221,11 @@ class Revolver(object):
+ self.pos = pos
+ self.child = child
+
+- def __call__(self, t, (w, h, cw, ch)):
++ 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
+diff --git a/renpy/display/particle.py b/renpy/display/particle.py
+index f821dc8..959fce1 100644
+--- a/renpy/display/particle.py
++++ b/renpy/display/particle.py
+@@ -295,7 +295,7 @@ class SpriteManager(renpy.display.core.Displayable):
+ return rv
+
+ def event(self, ev, x, y, st):
+- for i in xrange(len(self.children) -1, -1, -1):
++ for i in range(len(self.children) -1, -1, -1):
+ s = self.children[i]
+
+ if s.events:
+@@ -432,7 +432,7 @@ class SnowBlossomFactory(renpy.python.NoRollback):
+ self.init()
+
+ def init(self):
+- self.starts = [ random.uniform(0, self.start) for _i in xrange(0, self.count) ] # W0201
++ self.starts = [ random.uniform(0, self.start) for _i in range(0, self.count) ] # W0201
+ self.starts.append(self.start)
+ self.starts.sort()
+
+@@ -447,7 +447,7 @@ class SnowBlossomFactory(renpy.python.NoRollback):
+ if (st == 0) and not particles and self.fast:
+ rv = [ ]
+
+- for _i in xrange(0, self.count):
++ for _i in range(0, self.count):
+ rv.append(SnowBlossomParticle(self.image,
+ ranged(self.xspeed),
+ ranged(self.yspeed),
+diff --git a/renpy/display/pgrender.py b/renpy/display/pgrender.py
+index 9418095..c86000e 100644
+--- a/renpy/display/pgrender.py
++++ b/renpy/display/pgrender.py
+@@ -87,14 +87,14 @@ class Surface(pygame.Surface):
+ rv = pygame.Surface.subsurface(self, rect)
+ return rv
+
+-def surface((width, height), alpha):
++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]
+
+diff --git a/renpy/display/predict.py b/renpy/display/predict.py
+index 2b37a9f..305e0b5 100644
+--- a/renpy/display/predict.py
++++ b/renpy/display/predict.py
+@@ -120,7 +120,7 @@ def prediction_coroutine(root_widget):
+ if len(renpy.game.contexts) >= 2:
+ sls = renpy.game.contexts[-2].scene_lists
+
+- for l in sls.layers.itervalues():
++ for l in sls.layers.values():
+ for sle in l:
+ try:
+ displayable(sle.displayable)
+@@ -138,7 +138,7 @@ def prediction_coroutine(root_widget):
+
+
+ # Predict screens given with renpy.start_predict_screen.
+- for name, value in renpy.store._predict_screen.items():
++ for name, value in list(renpy.store._predict_screen.items()):
+ args, kwargs = value
+
+ renpy.display.screen.predict_screen(name, *args, **kwargs)
+diff --git a/renpy/display/screen.py b/renpy/display/screen.py
+index 585c6bf..622ba60 100644
+--- a/renpy/display/screen.py
++++ b/renpy/display/screen.py
+@@ -103,7 +103,7 @@ class ScreenProfile(renpy.object.Object):
+ self.const = const
+
+ if name is not None:
+- if isinstance(name, basestring):
++ if isinstance(name, str):
+ name = tuple(name.split())
+ profile[name] = self
+
+@@ -116,7 +116,7 @@ def get_profile(name):
+ A string or tuple.
+ """
+
+- if isinstance(name, basestring):
++ if isinstance(name, str):
+ name = tuple(name.split())
+
+ if name in profile:
+@@ -202,7 +202,7 @@ class Screen(renpy.object.Object):
+ location=None):
+
+ # The name of this screen.
+- if isinstance(name, basestring):
++ if isinstance(name, str):
+ name = tuple(name.split())
+
+ self.name = name
+@@ -700,7 +700,7 @@ def get_all_screen_variants(name):
+
+ rv = [ ]
+
+- for k, v in screens.iteritems():
++ for k, v in screens.items():
+ if k[0] == name:
+ rv.append((k[1], v))
+
+@@ -733,7 +733,7 @@ def sort_screens():
+ # For each screen, the set of screens that use it.
+ reverse = collections.defaultdict(set)
+
+- for k, v in screens.items():
++ for k, v in list(screens.items()):
+
+ name = k[0]
+
+@@ -751,7 +751,7 @@ def sort_screens():
+
+ rv = [ ]
+
+- workset = { k for k, v in depends.items() if not len(v) }
++ workset = { k for k, v in list(depends.items()) if not len(v) }
+
+ while workset:
+ name = workset.pop()
+@@ -782,7 +782,7 @@ def sorted_variants():
+ rv = [ ]
+
+ for name in sort_screens():
+- rv.extend(screens_by_name[name].values())
++ rv.extend(list(screens_by_name[name].values()))
+
+ return rv
+
+@@ -892,7 +892,7 @@ def get_screen(name, layer="screens"):
+
+ """
+
+- if isinstance(name, basestring):
++ if isinstance(name, str):
+ name = (name, )
+
+ sl = renpy.exports.scene_lists()
+@@ -1057,7 +1057,7 @@ def predict_screen(_screen_name, *_args, **kwargs):
+ if renpy.config.debug_image_cache:
+ import traceback
+
+- print "While predicting screen", _screen_name
++ print("While predicting screen", _screen_name)
+ traceback.print_exc()
+
+ renpy.ui.reset()
+@@ -1175,7 +1175,7 @@ def before_restart():
+ longer defined.
+ """
+
+- for k, layer in renpy.display.interface.old_scene.iteritems():
++ for k, layer in renpy.display.interface.old_scene.items():
+ if k is None:
+ continue
+
+diff --git a/renpy/display/swdraw.py b/renpy/display/swdraw.py
+index 1f620f1..a90e524 100644
+--- a/renpy/display/swdraw.py
++++ b/renpy/display/swdraw.py
+@@ -303,7 +303,7 @@ def draw_special(what, dest, x, y):
+
+ ramp = "\x00" * 256
+
+- for i in xrange(0, ramplen):
++ for i in range(0, ramplen):
+ ramp += chr(255 * i / ramplen)
+
+ ramp += "\xff" * 256
+diff --git a/renpy/dump.py b/renpy/dump.py
+index f047146..c01a960 100644
+--- a/renpy/dump.py
++++ b/renpy/dump.py
+@@ -113,14 +113,14 @@ def dump(error):
+ # Labels.
+ label = location["label"] = { }
+
+- for name, n in renpy.game.script.namemap.iteritems():
++ for name, n in renpy.game.script.namemap.items():
+ filename = n.filename
+ line = n.linenumber
+
+- if not isinstance(name, basestring):
++ if not isinstance(name, str):
+ continue
+
+- if not filter(name, filename):
++ if not list(filter(name, filename)):
+ continue
+
+ label[name] = [ filename, line ]
+@@ -130,7 +130,7 @@ def dump(error):
+ define = location["define"] = { }
+
+ for name, filename, line in definitions:
+- if not filter(name, filename):
++ if not list(filter(name, filename)):
+ continue
+
+ define[name] = [ filename, line ]
+@@ -139,7 +139,7 @@ def dump(error):
+ screen = location["screen"] = { }
+
+ for name, filename, line in screens:
+- if not filter(name, filename):
++ if not list(filter(name, filename)):
+ continue
+
+ screen[name] = [ filename, line ]
+@@ -148,7 +148,7 @@ def dump(error):
+ transform = location["transform"] = { }
+
+ for name, filename, line in transforms:
+- if not filter(name, filename):
++ if not list(filter(name, filename)):
+ continue
+
+ transform[name] = [ filename, line ]
+@@ -166,16 +166,16 @@ def dump(error):
+ """
+
+ if inspect.isfunction(o):
+- return inspect.getfile(o), o.func_code.co_firstlineno
++ return inspect.getfile(o), o.__code__.co_firstlineno
+
+ if inspect.ismethod(o):
+- return get_line(o.im_func)
++ return get_line(o.__func__)
+
+ return None, None
+
+ code = location["callable"] = { }
+
+- for modname, mod in sys.modules.items():
++ for modname, mod in list(sys.modules.items()):
+
+ if mod is None:
+ continue
+@@ -187,7 +187,7 @@ def dump(error):
+ else:
+ continue
+
+- for name, o in mod.__dict__.items():
++ for name, o in list(mod.__dict__.items()):
+
+ if inspect.isfunction(o):
+ try:
+@@ -199,7 +199,7 @@ def dump(error):
+ if filename is None:
+ continue
+
+- if not filter(name, filename):
++ if not list(filter(name, filename)):
+ continue
+
+ code[prefix + name] = [ filename, line ]
+@@ -208,7 +208,7 @@ def dump(error):
+
+ if inspect.isclass(o):
+
+- for methname, method in o.__dict__.iteritems():
++ for methname, method in o.__dict__.items():
+
+ try:
+ if inspect.getmodule(method) != mod:
+@@ -219,10 +219,10 @@ def dump(error):
+ if filename is None:
+ continue
+
+- if not filter(name, filename):
++ if not list(filter(name, filename)):
+ continue
+
+- if not filter(methname, filename):
++ if not list(filter(methname, filename)):
+ continue
+
+ code[prefix + name + "." + methname] = [ filename, line ]
+@@ -236,7 +236,7 @@ def dump(error):
+ pass
+
+ if args.json_dump != "-":
+- with file(args.json_dump, "w") as f:
++ with open(args.json_dump, "w") as f:
+ json.dump(result, f)
+ else:
+ json.dump(result, sys.stdout, indent=2)
+diff --git a/renpy/easy.py b/renpy/easy.py
+index 6aacea1..85f3d17 100644
+--- a/renpy/easy.py
++++ b/renpy/easy.py
+@@ -36,7 +36,7 @@ def displayable_or_none(d, scope=None):
+ if d is None:
+ return d
+
+- if isinstance(d, basestring):
++ if isinstance(d, str):
+ if not d:
+ raise Exception("An empty string cannot be used as a displayable.")
+ elif ("[" in d) and renpy.config.dynamic_images:
+@@ -70,7 +70,7 @@ def displayable(d, scope=None):
+ if isinstance(d, renpy.display.core.Displayable):
+ return d
+
+- if isinstance(d, basestring):
++ if isinstance(d, str):
+ if not d:
+ raise Exception("An empty string cannot be used as a displayable.")
+ elif ("[" in d) and renpy.config.dynamic_images:
+@@ -97,7 +97,7 @@ def dynamic_image(d, scope=None):
+ Substitutes a scope into `d`, then returns a displayable.
+ """
+
+- if isinstance(d, basestring):
++ if isinstance(d, str):
+ d = renpy.substitutions.substitute(d, scope=scope, force=True, translate=False)[0]
+
+ return displayable_or_none(d)
+@@ -114,7 +114,7 @@ def predict(d):
+ def timed(name):
+ start = time.time()
+ yield
+- print "{0}: {1:.2f} ms".format(name, (time.time() - start) * 1000.0)
++ print("{0}: {1:.2f} ms".format(name, (time.time() - start) * 1000.0))
+
+
+ def split_properties(properties, *prefixes):
+@@ -145,7 +145,7 @@ def split_properties(properties, *prefixes):
+
+ prefix_d = list(zip(prefixes, rv))
+
+- for k, v in properties.iteritems():
++ for k, v in properties.items():
+ for prefix, d in prefix_d:
+ if k.startswith(prefix):
+ d[k[len(prefix):]] = v
+diff --git a/renpy/editor.py b/renpy/editor.py
+index 7be4c32..bac80f7 100644
+--- a/renpy/editor.py
++++ b/renpy/editor.py
+@@ -114,7 +114,7 @@ def init():
+ return
+
+ scope = { "__file__" : path }
+- execfile(path, scope, scope)
++ exec(compile(open(path).read(), path, 'exec'), scope, scope)
+
+ if "Editor" in scope:
+ editor = scope["Editor"]()
+diff --git a/renpy/error.py b/renpy/error.py
+index d70fe2a..6e56038 100644
+--- a/renpy/error.py
++++ b/renpy/error.py
+@@ -23,7 +23,7 @@
+
+ import traceback
+ import sys
+-import cStringIO
++import io
+ import platform
+ import linecache
+
+@@ -43,8 +43,8 @@ def write_utf8_traceback_list(out, l):
+ for filename, line, what, text in l:
+
+ # Filename is either unicode or an fsecoded string.
+- if not isinstance(filename, unicode):
+- filename = unicode(filename, FSENCODING, "replace")
++ if not isinstance(filename, str):
++ filename = str(filename, FSENCODING, "replace")
+
+ # Line is a number.
+
+@@ -127,13 +127,13 @@ def open_error_file(fn, mode):
+
+ try:
+ new_fn = os.path.join(renpy.config.logdir, fn)
+- f = file(new_fn, mode)
++ f = open(new_fn, mode)
+ return f, new_fn
+ except:
+ pass
+
+ try:
+- f = file(fn, mode)
++ f = open(fn, mode)
+ return f, fn
+ except:
+ pass
+@@ -141,7 +141,7 @@ def open_error_file(fn, mode):
+ import tempfile
+
+ new_fn = os.path.join(tempfile.gettempdir(), "renpy-" + fn)
+- return file(new_fn, mode), new_fn
++ return open(new_fn, mode), new_fn
+
+ def report_exception(e, editor=True):
+ """
+@@ -157,11 +157,11 @@ def report_exception(e, editor=True):
+
+ type, _value, tb = sys.exc_info() #@ReservedAssignment
+
+- print(repr(e))
++ print((repr(e)))
+
+ def safe_utf8(e):
+ try:
+- m = unicode(e)
++ m = str(e)
+ except:
+ try:
+ if len(e.args) == 0:
+@@ -176,27 +176,27 @@ def report_exception(e, editor=True):
+ except:
+ m = "<Could not encode exception.>"
+
+- if isinstance(m, unicode):
++ if isinstance(m, str):
+ return m.encode("utf-8", "replace")
+ else:
+ return m
+
+ # Return values - which can be displayed to the user.
+- simple = cStringIO.StringIO()
+- full = cStringIO.StringIO()
++ simple = io.StringIO()
++ full = io.StringIO()
+
+ full_tl = traceback_list(tb)
+ simple_tl = filter_traceback_list(full_tl)
+
+- print >>simple, renpy.game.exception_info
++ print(renpy.game.exception_info, file=simple)
+ write_utf8_traceback_list(simple, simple_tl)
+- print >>simple, type.__name__ + ":",
+- print >>simple, safe_utf8(e)
++ print(type.__name__ + ":", end=' ', file=simple)
++ print(safe_utf8(e), file=simple)
+
+- print >>full, "Full traceback:"
++ print("Full traceback:", file=full)
+ write_utf8_traceback_list(full, full_tl)
+- print >>full, type.__name__ + ":",
+- print >>full, safe_utf8(e)
++ print(type.__name__ + ":", end=' ', file=full)
++ print(safe_utf8(e), file=full)
+
+ # Write to stdout/stderr.
+ sys.stdout.write("\n")
+@@ -204,11 +204,11 @@ def report_exception(e, editor=True):
+ sys.stdout.write("\n")
+ sys.stdout.write(simple.getvalue())
+
+- print >>full
++ print(file=full)
+ try:
+- print >>full, platform.platform()
+- print >>full, renpy.version
+- print >>full, renpy.config.name + " " + renpy.config.version
++ print(platform.platform(), file=full)
++ print(renpy.version, file=full)
++ print(renpy.config.name + " " + renpy.config.version, file=full)
+ except:
+ pass
+
+@@ -223,14 +223,14 @@ def report_exception(e, editor=True):
+
+ f.write(codecs.BOM_UTF8)
+
+- print >>f, "I'm sorry, but an uncaught exception occurred."
+- print >>f
++ print("I'm sorry, but an uncaught exception occurred.", file=f)
++ print(file=f)
+
+ f.write(simple)
+
+- print >>f
+- print >>f, "-- Full Traceback ------------------------------------------------------------"
+- print >>f
++ print(file=f)
++ print("-- Full Traceback ------------------------------------------------------------", file=f)
++ print(file=f)
+
+ f.write(full)
+ f.close()
+diff --git a/renpy/execution.py b/renpy/execution.py
+index 224c631..a41b59a 100644
+--- a/renpy/execution.py
++++ b/renpy/execution.py
+@@ -210,7 +210,7 @@ class Context(renpy.object.Object):
+
+ vars(self.info).update(vars(context.info))
+
+- for k, v in context.music.iteritems():
++ for k, v in context.music.items():
+ self.music[k] = v.copy()
+
+ self.images = renpy.display.image.ShownImageInfo(context.images)
+@@ -279,7 +279,7 @@ class Context(renpy.object.Object):
+
+ dynamic = self.dynamic_stack.pop()
+
+- for k, v in dynamic.iteritems():
++ for k, v in dynamic.items():
+ if isinstance(v, Delete):
+ del store[k]
+ else:
+@@ -390,14 +390,14 @@ class Context(renpy.object.Object):
+ if developer and self.next_node:
+ self.check_stacks()
+
+- except renpy.game.CONTROL_EXCEPTIONS, e:
++ except renpy.game.CONTROL_EXCEPTIONS as e:
+
+ # An exception ends the current translation.
+ self.translate_interaction = None
+
+ raise
+
+- except Exception, e:
++ except Exception as e:
+ self.translate_interaction = None
+
+ exc_info = sys.exc_info()
+@@ -408,18 +408,18 @@ class Context(renpy.object.Object):
+ self.exception_handler(short, full, traceback_fn)
+ elif renpy.display.error.report_exception(short, full, traceback_fn):
+ raise
+- except renpy.game.CONTROL_EXCEPTIONS, ce:
++ except renpy.game.CONTROL_EXCEPTIONS as ce:
+ raise ce
+- except Exception, ce:
+- raise exc_info[0], exc_info[1], exc_info[2]
++ except Exception as ce:
++ raise exc_info[0](exc_info[1]).with_traceback(exc_info[2])
+
+ node = self.next_node
+
+- except renpy.game.JumpException, e:
++ except renpy.game.JumpException as e:
+ node = renpy.game.script.lookup(e.args[0])
+ self.abnormal = True
+
+- except renpy.game.CallException, e:
++ except renpy.game.CallException as e:
+
+ if self.next_node is None:
+ raise Exception("renpy.call can't be used when the next node is undefined.")
+@@ -495,7 +495,7 @@ class Context(renpy.object.Object):
+ if renpy.game.script.has_label(self.return_stack[-1]):
+ node = renpy.game.script.lookup(self.return_stack[-1])
+ elif renpy.game.script.has_label(self.call_location_stack[-1]):
+- node = renpy.game.script.lookup(self.call_location_stack[-1]).next
++ node = renpy.game.script.lookup(self.call_location_stack[-1]).__next__
+
+ if node is None:
+
+@@ -629,9 +629,9 @@ class Context(renpy.object.Object):
+ if renpy.config.debug_image_cache:
+ import traceback
+
+- print
++ print()
+ traceback.print_exc()
+- print "While predicting images."
++ print("While predicting images.")
+
+ self.images = old_images
+ self.predict_return_stack = None
+diff --git a/renpy/exports.py b/renpy/exports.py
+index faccd36..61f3c40 100644
+--- a/renpy/exports.py
++++ b/renpy/exports.py
+@@ -39,7 +39,7 @@ def renpy_pure(fn):
+
+ name = fn
+
+- if not isinstance(name, basestring):
++ if not isinstance(name, str):
+ name = fn.__name__
+
+ pure("renpy." + name)
+@@ -371,7 +371,7 @@ def copy_images(old, new):
+
+ lenold = len(old)
+
+- for k, v in renpy.display.image.images.items():
++ for k, v in list(renpy.display.image.images.items()):
+ if len(k) < lenold:
+ continue
+
+@@ -495,7 +495,7 @@ def predict_show(name, layer=None, what=None, tag=None, at_list=[ ]):
+
+ if what is None:
+ what = name
+- elif isinstance(what, basestring):
++ elif isinstance(what, str):
+ what = tuple(what.split())
+
+ if isinstance(what, renpy.display.core.Displayable):
+@@ -593,7 +593,7 @@ def show(name, at_list=[ ], layer=None, what=None, zorder=None, tag=None, behind
+
+ if what is None:
+ what = name
+- elif isinstance(what, basestring):
++ elif isinstance(what, str):
+ what = tuple(what.split())
+
+ if isinstance(what, renpy.display.core.Displayable):
+@@ -740,7 +740,7 @@ def input(prompt, default='', allow=None, exclude='{}', length=None, with_none=N
+ renpy.exports.mode('input')
+
+ roll_forward = renpy.exports.roll_forward_info()
+- if not isinstance(roll_forward, basestring):
++ if not isinstance(roll_forward, str):
+ roll_forward = None
+
+ # use previous data in rollback
+@@ -1038,7 +1038,7 @@ class TagQuotingDict(object):
+ if key in store:
+ rv = store[key]
+
+- if isinstance(rv, (str, unicode)):
++ if isinstance(rv, str):
+ rv = rv.replace("{", "{{")
+
+ return rv
+@@ -1060,7 +1060,7 @@ def predict_say(who, what):
+ if who is None:
+ who = renpy.store.narrator # E1101 @UndefinedVariable
+
+- if isinstance(who, (str, unicode)):
++ if isinstance(who, str):
+ return renpy.store.predict_say(who, what)
+
+ predict = getattr(who, 'predict', None)
+@@ -1115,7 +1115,7 @@ def say(who, what, interact=True):
+ if who is None:
+ who = renpy.store.narrator # E1101 @UndefinedVariable
+
+- if isinstance(who, (str, unicode)):
++ if isinstance(who, str):
+ renpy.store.say(who, what, interact=interact)
+ else:
+ who(what, interact=interact)
+@@ -1444,8 +1444,8 @@ def get_all_labels():
+ """
+ rv = [ ]
+
+- for i in renpy.game.script.namemap.iterkeys():
+- if isinstance(i, basestring):
++ for i in renpy.game.script.namemap.keys():
++ if isinstance(i, str):
+ rv.append(i)
+
+ return renpy.python.RevertableSet(rv)
+@@ -1792,7 +1792,7 @@ def log(msg):
+
+ import textwrap
+
+- print >>logfile, textwrap.fill(msg, renpy.config.log_width).encode("utf-8")
++ print(textwrap.fill(msg, renpy.config.log_width).encode("utf-8"), file=logfile)
+ logfile.flush()
+
+ except:
+@@ -1908,7 +1908,7 @@ def seen_image(name):
+ return name in renpy.game.persistent._seen_images # @UndefinedVariable
+
+
+-def file(fn): #@ReservedAssignment
++def open(fn): #@ReservedAssignment
+ """
+ :doc: file
+
+@@ -1958,7 +1958,7 @@ def get_at_list(name, layer=None):
+ If `layer` is None, uses the default layer for the given tag.
+ """
+
+- if isinstance(name, basestring):
++ if isinstance(name, str):
+ name = tuple(name.split())
+
+ tag = name[0]
+@@ -2173,7 +2173,7 @@ def load_string(s, filename="<string>"):
+ old_locked = renpy.config.locked
+ renpy.config.locked = False
+
+- stmts, initcode = renpy.game.script.load_string(filename, unicode(s))
++ stmts, initcode = renpy.game.script.load_string(filename, str(s))
+
+ if stmts is None:
+ return None
+@@ -2459,7 +2459,7 @@ def call_screen(_screen_name, *args, **kwargs):
+
+ try:
+ rv = renpy.ui.interact(mouse="screen", type="screen", roll_forward=roll_forward)
+- except (renpy.game.JumpException, renpy.game.CallException), e:
++ except (renpy.game.JumpException, renpy.game.CallException) as e:
+ rv = e
+
+ renpy.exports.checkpoint(rv)
+@@ -2616,7 +2616,7 @@ def variant(name):
+ returns True if any of the variants is selected.
+ """
+
+- if isinstance(name, basestring):
++ if isinstance(name, str):
+ return name in renpy.config.variants
+ else:
+ for n in name:
+@@ -2745,7 +2745,7 @@ def fsencode(s):
+ Converts s from unicode to the filesystem encoding.
+ """
+
+- if not isinstance(s, unicode):
++ if not isinstance(s, str):
+ return s
+
+ fsencoding = sys.getfilesystemencoding() or "utf-8"
+diff --git a/renpy/game.py b/renpy/game.py
+index d66800c..1ba0fe5 100644
+--- a/renpy/game.py
++++ b/renpy/game.py
+@@ -264,7 +264,7 @@ def invoke_in_new_context(callable, *args, **kwargs): #@ReservedAssignment
+
+ return callable(*args, **kwargs)
+
+- except renpy.game.JumpOutException, e:
++ except renpy.game.JumpOutException as e:
+
+ raise renpy.game.JumpException(e.args[0])
+
+@@ -312,7 +312,7 @@ def call_in_new_context(label, *args, **kwargs):
+ context.goto_label(label)
+ return renpy.execution.run_context(False)
+
+- except renpy.game.JumpOutException, e:
++ except renpy.game.JumpOutException as e:
+
+ raise renpy.game.JumpException(e.args[0])
+
+@@ -350,7 +350,7 @@ def call_replay(label, scope={}):
+
+ renpy.exports.execute_default_statement(True)
+
+- for k, v in scope.iteritems():
++ for k, v in scope.items():
+ setattr(renpy.store, k, v)
+
+ renpy.store._in_replay = label
+diff --git a/renpy/lint.py b/renpy/lint.py
+index 088b89c..4ef99d2 100644
+--- a/renpy/lint.py
++++ b/renpy/lint.py
+@@ -28,7 +28,7 @@ import sys
+ import collections
+ import textwrap
+
+-import __builtin__
++import builtins
+
+ python_builtins = set(dir(__builtin__))
+ renpy_builtins = set()
+@@ -53,13 +53,13 @@ report_node = None
+ # Reports a message to the user.
+ def report(msg, *args):
+ if report_node:
+- out = u"%s:%d " % (renpy.parser.unicode_filename(report_node.filename), report_node.linenumber)
++ out = "%s:%d " % (renpy.parser.unicode_filename(report_node.filename), report_node.linenumber)
+ else:
+ out = ""
+
+ out += msg % args
+- print
+- print out.encode('utf-8')
++ print()
++ print(out.encode('utf-8'))
+
+ added = { }
+
+@@ -68,7 +68,7 @@ added = { }
+ def add(msg):
+ if not msg in added:
+ added[msg] = True
+- print unicode(msg).encode('utf-8')
++ print(str(msg).encode('utf-8'))
+
+
+ # Trys to evaluate an expression, announcing an error if it fails.
+@@ -473,7 +473,7 @@ def check_define(node, kind):
+ def check_style(name, s):
+
+ for p in s.properties:
+- for k, v in p.iteritems():
++ for k, v in p.items():
+
+ kname = name + ", property " + k
+
+@@ -504,7 +504,7 @@ def check_label(node):
+
+
+ def check_styles():
+- for full_name, s in renpy.style.styles.iteritems(): # @UndefinedVariable
++ for full_name, s in renpy.style.styles.items(): # @UndefinedVariable
+ name = "style." + full_name[0]
+ for i in full_name[1:]:
+ name += "[{!r}]".format(i)
+@@ -588,8 +588,8 @@ def lint():
+
+ renpy.game.lint = True
+
+- print codecs.BOM_UTF8
+- print unicode(renpy.version + " lint report, generated at: " + time.ctime()).encode("utf-8")
++ print(codecs.BOM_UTF8)
++ print(str(renpy.version + " lint report, generated at: " + time.ctime()).encode("utf-8"))
+
+ # This supports check_hide.
+ global image_prefixes
+@@ -723,10 +723,10 @@ characters per block. """.format(
+ lines.append(s)
+
+
+- print
+- print
+- print "Statistics:"
+- print
++ print()
++ print()
++ print("Statistics:")
++ print()
+
+ languages = list(counts)
+ languages.sort()
+@@ -738,17 +738,17 @@ characters per block. """.format(
+
+ for l in lines:
+ for ll in textwrap.wrap(l, 78):
+- print ll.encode("utf-8")
++ print(ll.encode("utf-8"))
+
+- print
++ print()
+
+- print
++ print()
+ if renpy.config.developer:
+- print "Remember to set config.developer to False before releasing."
+- print
++ print("Remember to set config.developer to False before releasing.")
++ print()
+
+- print "Lint is not a substitute for thorough testing. Remember to update Ren'Py"
+- print "before releasing. New releases fix bugs and improve compatibility."
++ print("Lint is not a substitute for thorough testing. Remember to update Ren'Py")
++ print("before releasing. New releases fix bugs and improve compatibility.")
+
+ return False
+
+diff --git a/renpy/loader.py b/renpy/loader.py
+index 0760219..f3a7a7b 100644
+--- a/renpy/loader.py
++++ b/renpy/loader.py
+@@ -22,7 +22,7 @@
+ import renpy
+ import os.path
+ from pickle import loads
+-from cStringIO import StringIO
++from io import StringIO
+ import sys
+ import types
+ import threading
+@@ -30,7 +30,7 @@ import zlib
+
+ # Ensure the utf-8 codec is loaded, to prevent recursion when we use it
+ # to look up filenames.
+-u"".encode("utf-8")
++"".encode("utf-8")
+
+
+ ################################################################# Physical Paths
+@@ -61,7 +61,7 @@ try:
+
+ expansion = os.environ.get("ANDROID_EXPANSION", None)
+ if expansion is not None:
+- print "Using expansion file", expansion
++ print("Using expansion file", expansion)
+
+ apks = [
+ android.apk.APK(apk=expansion, prefix='assets/x-game/'),
+@@ -71,7 +71,7 @@ try:
+ game_apks = [ apks[0] ]
+
+ else:
+- print "Not using expansion file."
++ print("Not using expansion file.")
+
+ apks = [
+ android.apk.APK(prefix='assets/x-game/'),
+@@ -121,7 +121,7 @@ def index_archives():
+
+ try:
+ fn = transfn(prefix + ".rpa")
+- f = file(fn, "rb")
++ f = open(fn, "rb")
+ l = f.readline()
+
+ # 3.0 Branch.
+@@ -133,7 +133,7 @@ def index_archives():
+
+ # Deobfuscate the index.
+
+- for k in index.keys():
++ for k in list(index.keys()):
+
+ if len(index[k][0]) == 2:
+ index[k] = [ (offset ^ key, dlen ^ key) for offset, dlen in index[k] ]
+@@ -158,7 +158,7 @@ def index_archives():
+ f.close()
+
+ fn = transfn(prefix + ".rpi")
+- index = loads(file(fn, "rb").read().decode("zlib"))
++ index = loads(open(fn, "rb").read().decode("zlib"))
+ archives.append((prefix, index))
+ except:
+ raise
+@@ -253,7 +253,7 @@ def scandirfiles():
+ files = game_files
+
+ for _prefix, index in archives:
+- for j in index.iterkeys():
++ for j in index.keys():
+ add(None, j)
+
+
+@@ -380,7 +380,7 @@ class SubFile(object):
+ def __iter__(self):
+ return self
+
+- def next(self): #@ReservedAssignment
++ def __next__(self): #@ReservedAssignment
+ rv = self.readline()
+
+ if not rv:
+@@ -489,7 +489,7 @@ def load_core(name):
+
+ # Compatibility path.
+ else:
+- f = file(afn, "rb")
++ f = open(afn, "rb")
+
+ for offset, dlen in index[name]:
+ f.seek(offset)
+@@ -684,13 +684,13 @@ class RenpyImporter(object):
+ mod.__path__ = [ filename[:-len("__init__.py")] ]
+
+ source = load(filename).read().decode("utf8")
+- if source and source[0] == u'\ufeff':
++ if source and source[0] == '\ufeff':
+ source = source[1:]
+ source = source.encode("raw_unicode_escape")
+
+ source = source.replace("\r", "")
+ code = compile(source, filename, 'exec')
+- exec code in mod.__dict__
++ exec(code, mod.__dict__)
+ return mod
+
+ def get_data(self, filename):
+@@ -773,7 +773,7 @@ def auto_thread_function():
+ if auto_quit_flag:
+ return
+
+- items = auto_mtimes.items()
++ items = list(auto_mtimes.items())
+
+ for fn, mtime in items:
+
+diff --git a/renpy/loadsave.py b/renpy/loadsave.py
+index 862105d..07fd357 100644
+--- a/renpy/loadsave.py
++++ b/renpy/loadsave.py
+@@ -22,9 +22,9 @@
+ # This file contains functions that load and save the game state.
+
+ import pickle
+-import cPickle
++import pickle
+
+-from cStringIO import StringIO
++from io import StringIO
+
+ import zipfile
+ import re
+@@ -40,13 +40,13 @@ from json import dumps as json_dumps
+ # Dump that chooses which pickle to use:
+ def dump(o, f):
+ if renpy.config.use_cpickle:
+- cPickle.dump(o, f, cPickle.HIGHEST_PROTOCOL)
++ pickle.dump(o, f, pickle.HIGHEST_PROTOCOL)
+ else:
+ pickle.dump(o, f, pickle.HIGHEST_PROTOCOL)
+
+ def loads(s):
+ if renpy.config.use_cpickle:
+- return cPickle.loads(s)
++ return pickle.loads(s)
+ else:
+ return pickle.loads(s)
+
+@@ -71,10 +71,10 @@ def save_dump(roots, log):
+ f.write("{0: 7d} {1} = alias {2}\n".format(0, path, o_repr_cache[ido]))
+ return 0
+
+- if isinstance(o, (int, float, types.NoneType, types.ModuleType, types.ClassType)):
++ if isinstance(o, (int, float, type(None), types.ModuleType, type)):
+ o_repr = repr(o)
+
+- elif isinstance(o, (str, unicode)):
++ elif isinstance(o, str):
+ if len(o) <= 80:
+ o_repr = repr(o).encode("utf-8")
+ else:
+@@ -87,7 +87,7 @@ def save_dump(roots, log):
+ o_repr = "<" + o.__class__.__name__ + ">"
+
+ elif isinstance(o, types.MethodType):
+- o_repr = "<method {0}.{1}>".format(o.im_class.__name__, o.im_func.__name__)
++ o_repr = "<method {0}.{1}>".format(o.__self__.__class__.__name__, o.__func__.__name__)
+
+ elif isinstance(o, object):
+ o_repr = "<{0}>".format(type(o).__name__)
+@@ -98,10 +98,10 @@ def save_dump(roots, log):
+
+ o_repr_cache[ido] = o_repr
+
+- if isinstance(o, (int, float, types.NoneType, types.ModuleType, types.ClassType)):
++ if isinstance(o, (int, float, type(None), types.ModuleType, type)):
+ size = 1
+
+- elif isinstance(o, (str, unicode)):
++ elif isinstance(o, str):
+ size = len(o) / 40 + 1
+
+ elif isinstance(o, (tuple, list)):
+@@ -112,12 +112,12 @@ def save_dump(roots, log):
+
+ elif isinstance(o, dict):
+ size = 2
+- for k, v in o.iteritems():
++ for k, v in o.items():
+ size += 2
+ size += visit(v, "{0}[{1!r}]".format(path, k))
+
+ elif isinstance(o, types.MethodType):
+- size = 1 + visit(o.im_self, path + ".im_self")
++ size = 1 + visit(o.__self__, path + ".im_self")
+
+ else:
+
+@@ -141,7 +141,7 @@ def save_dump(roots, log):
+
+ state = get(2, { })
+ if isinstance(state, dict):
+- for k, v in state.iteritems():
++ for k, v in state.items():
+ size += 2
+ size += visit(v, path + "." + k)
+ else:
+@@ -166,7 +166,7 @@ def save_dump(roots, log):
+
+ return size
+
+- f = file("save_dump.txt", "w")
++ f = open("save_dump.txt", "w")
+
+ visit(roots, "roots")
+ visit(log, "log")
+@@ -712,7 +712,7 @@ def clear_cache():
+ Clears the entire cache.
+ """
+
+- for c in cache.values():
++ for c in list(cache.values()):
+ c.clear()
+
+ newest_slot_cache.clear()
+diff --git a/renpy/log.py b/renpy/log.py
+index 87f349e..8f34999 100644
+--- a/renpy/log.py
++++ b/renpy/log.py
+@@ -86,7 +86,7 @@ class LogFile(object):
+ altfn = os.path.join(tempfile.gettempdir(), "renpy-" + self.name + ".txt")
+
+ if renpy.android:
+- print "Logging to", fn
++ print("Logging to", fn)
+
+ if self.append:
+ mode = "a"
+@@ -132,7 +132,7 @@ class LogFile(object):
+ s = s % args
+ s += "\n"
+
+- if not isinstance(s, unicode):
++ if not isinstance(s, str):
+ s = s.decode("latin-1")
+
+ s = s.replace("\n", "\r\n")
+diff --git a/renpy/main.py b/renpy/main.py
+index f8bee19..721eecc 100644
+--- a/renpy/main.py
++++ b/renpy/main.py
+@@ -39,7 +39,7 @@ def log_clock(s):
+
+ renpy.display.log.write(s)
+ if renpy.android and not renpy.config.log_to_stdout:
+- print s
++ print(s)
+
+ last_clock = now
+
+@@ -146,7 +146,7 @@ def load_rpe(fn):
+ zfn.close()
+
+ sys.path.insert(0, fn)
+- exec autorun in dict()
++ exec(autorun, dict())
+
+ def choose_variants():
+
+@@ -174,10 +174,10 @@ def choose_variants():
+ manufacturer = Build.MANUFACTURER
+ model = Build.MODEL
+
+- print "Manufacturer", manufacturer, "model", model
++ print("Manufacturer", manufacturer, "model", model)
+
+ if manufacturer == "Amazon" and model.startswith("AFT"):
+- print "Running on a Fire TV."
++ print("Running on a Fire TV.")
+ renpy.config.variants.insert(0, "firetv")
+ except:
+ pass
+@@ -186,7 +186,7 @@ def choose_variants():
+ package_manager = android.activity.getPackageManager()
+
+ if package_manager.hasSystemFeature("android.hardware.type.television"):
+- print "Running on a television."
++ print("Running on a television.")
+ renpy.config.variants.insert(0, "tv")
+ renpy.config.variants.insert(0, "small")
+ return
+@@ -198,7 +198,7 @@ def choose_variants():
+
+ info = renpy.display.get_info()
+ diag = math.hypot(info.current_w, info.current_h) / android.get_dpi()
+- print "Screen diagonal is", diag, "inches."
++ print("Screen diagonal is", diag, "inches.")
+
+ if diag >= 6:
+ renpy.config.variants.insert(0, 'tablet')
+@@ -216,7 +216,7 @@ def choose_variants():
+
+ idiom = UIDevice.currentDevice().userInterfaceIdiom
+
+- print "iOS device idiom", idiom
++ print("iOS device idiom", idiom)
+
+ # idiom 0 is iPhone, 1 is iPad. We assume any bigger idiom will
+ # be tablet-like.
+@@ -359,7 +359,7 @@ def main():
+ renpy.game.script = renpy.script.Script()
+ renpy.game.script.load_script()
+
+- print time.time() - start
++ print(time.time() - start)
+ sys.exit(0)
+
+ renpy.game.exception_info = 'After loading the script.'
+@@ -470,7 +470,7 @@ def main():
+ restart = (renpy.config.end_game_transition, "_invoke_main_menu", "_main_menu")
+ renpy.persistent.update(True)
+
+- except game.FullRestartException, e:
++ except game.FullRestartException as e:
+ restart = e.reason
+
+ finally:
+diff --git a/renpy/memory.py b/renpy/memory.py
+index 7751db1..c127e3f 100644
+--- a/renpy/memory.py
++++ b/renpy/memory.py
+@@ -83,7 +83,7 @@ def walk_memory(roots, seen=None):
+ get_referents = gc.get_referents
+ worklist_append = worklist.append
+
+- ignore_types = (types.ModuleType, types.ClassType, types.FunctionType)
++ ignore_types = (types.ModuleType, type, types.FunctionType)
+
+ while worklist:
+ name, o = worklist.pop(0)
+@@ -133,7 +133,7 @@ def profile_memory_common(packages=[ "renpy", "store" ]):
+ if mod_name.startswith("renpy.store"):
+ continue
+
+- for name, o in mod.__dict__.items():
++ for name, o in list(mod.__dict__.items()):
+ roots.append((mod_name + "." + name, o))
+
+ return walk_memory(roots)
+@@ -169,7 +169,7 @@ def profile_memory(fraction=1.0, minimum=0):
+ write("Memory profile at " + time.ctime() + ":")
+ write("")
+
+- usage = [ (v, k) for (k, v) in profile_memory_common()[0].items() ]
++ usage = [ (v, k) for (k, v) in list(profile_memory_common()[0].items()) ]
+ usage.sort()
+
+ # The total number of bytes allocated.
+@@ -223,7 +223,7 @@ def diff_memory(update=True):
+
+ diff = [ ]
+
+- for k, v in usage.iteritems():
++ for k, v in usage.items():
+ diff.append((
+ v - old_usage.get(k, 0),
+ k))
+@@ -275,8 +275,8 @@ def profile_rollback():
+ # Walk the log, finding new roots and rollback information.
+ for rb in log:
+
+- for store_name, store in rb.stores.iteritems():
+- for var_name, o in store.iteritems():
++ for store_name, store in rb.stores.items():
++ for var_name, o in store.items():
+ name = store_name + "." + var_name
+ id_o = id(o)
+
+@@ -300,7 +300,7 @@ def profile_rollback():
+
+ sizes = walk_memory(roots, seen)[0]
+
+- usage = [ (v, k) for (k, v) in sizes.iteritems() ]
++ usage = [ (v, k) for (k, v) in sizes.items() ]
+ usage.sort()
+
+ write("Total Bytes".rjust(13) + " " + "Per Rollback".rjust(13))
+@@ -344,15 +344,15 @@ def find_parents(cls):
+
+ objects.append(o)
+
+- print prefix + str(id(o)), type(o),
++ print(prefix + str(id(o)), type(o), end=' ')
+
+ try:
+ if isinstance(o, dict) and "__name__" in o:
+- print "with name", o["__name__"]
++ print("with name", o["__name__"])
+ else:
+- print repr(o)#[:1000]
++ print(repr(o))#[:1000]
+ except:
+- print "Bad repr."
++ print("Bad repr.")
+
+ found = False
+
+@@ -364,7 +364,7 @@ def find_parents(cls):
+ continue
+
+ if isinstance(o, weakref.WeakKeyDictionary):
+- for k, v in o.data.items():
++ for k, v in list(o.data.items()):
+ if v is objects[-4]:
+ k = k()
+ seen.add(id(k))
+@@ -390,7 +390,7 @@ def find_parents(cls):
+ break
+
+ if not found:
+- print "<no parent, popping>"
++ print("<no parent, popping>")
+
+ o, prefix = queue.pop()
+
+@@ -399,8 +399,8 @@ def find_parents(cls):
+ import random
+ if random.random() < .1:
+
+- print
+- print "==================================================="
+- print
++ print()
++ print("===================================================")
++ print()
+
+ print_path(o)
+diff --git a/renpy/parser.py b/renpy/parser.py
+index 0b48859..04c2a74 100644
+--- a/renpy/parser.py
++++ b/renpy/parser.py
+@@ -37,7 +37,7 @@ parse_errors = [ ]
+ class ParseError(Exception):
+
+ def __init__(self, filename, number, msg, line=None, pos=None, first=False):
+- message = u"File \"%s\", line %d: %s" % (unicode_filename(filename), number, msg)
++ message = "File \"%s\", line %d: %s" % (unicode_filename(filename), number, msg)
+
+ if line:
+ lines = line.split('\n')
+@@ -97,7 +97,7 @@ def unicode_filename(fn):
+ Converts the supplied filename to unicode.
+ """
+
+- if isinstance(fn, unicode):
++ if isinstance(fn, str):
+ return fn
+
+ # Windows.
+@@ -200,7 +200,7 @@ def list_logical_lines(filename, filedata=None, linenumber=1):
+ pos = 0
+
+ # Skip the BOM, if any.
+- if len(data) and data[0] == u'\ufeff':
++ if len(data) and data[0] == '\ufeff':
+ pos += 1
+
+ if renpy.game.context().init_phase:
+@@ -480,7 +480,7 @@ ESCAPED_OPERATORS = [
+
+ operator_regexp = "|".join([ re.escape(i) for i in OPERATORS ] + ESCAPED_OPERATORS)
+
+-word_regexp = ur'[a-zA-Z_\u00a0-\ufffd][0-9a-zA-Z_\u00a0-\ufffd]*'
++word_regexp = r'[a-zA-Z_\u00a0-\ufffd][0-9a-zA-Z_\u00a0-\ufffd]*'
+
+ class Lexer(object):
+ """
+@@ -564,7 +564,7 @@ class Lexer(object):
+
+ # print self.text[self.pos].encode('unicode_escape')
+
+- self.match_regexp(ur"(\s+|\\\n)+")
++ self.match_regexp(r"(\s+|\\\n)+")
+
+ def match(self, regexp):
+ """
+@@ -689,7 +689,7 @@ class Lexer(object):
+ s = s.replace("\\[", "[[")
+ s = s.replace("\\%", "%%")
+ s = re.sub(r'\\u([0-9a-fA-F]{1,4})',
+- lambda m : unichr(int(m.group(1), 16)), s)
++ lambda m : chr(int(m.group(1), 16)), s)
+ s = re.sub(r'\\(.)', r'\1', s)
+
+ return s
+@@ -1005,7 +1005,7 @@ class Lexer(object):
+ name = name or thing
+ rv = self.match(thing)
+ else:
+- name = name or thing.im_func.func_name
++ name = name or thing.__func__.__name__
+ rv = thing()
+
+ if rv is None:
+@@ -2315,7 +2315,7 @@ def parse_block(l):
+ else:
+ rv.append(stmt)
+
+- except ParseError, e:
++ except ParseError as e:
+ parse_errors.append(e.message)
+ l.advance()
+
+@@ -2339,7 +2339,7 @@ def parse(fn, filedata=None, linenumber=1):
+ try:
+ lines = list_logical_lines(fn, filedata, linenumber)
+ nested = group_logical_lines(lines)
+- except ParseError, e:
++ except ParseError as e:
+ parse_errors.append(e.message)
+ return None
+
+@@ -2371,9 +2371,9 @@ def report_parse_errors():
+ f, error_fn = renpy.error.open_error_file("errors.txt", "w")
+ f.write(codecs.BOM_UTF8)
+
+- print >>f, "I'm sorry, but errors were detected in your script. Please correct the"
+- print >>f, "errors listed below, and try again."
+- print >>f
++ print("I'm sorry, but errors were detected in your script. Please correct the", file=f)
++ print("errors listed below, and try again.", file=f)
++ print(file=f)
+
+ for i in parse_errors:
+
+@@ -2385,14 +2385,14 @@ def report_parse_errors():
+ except:
+ pass
+
+- print
+- print >>f
+- print i
+- print >>f, i
++ print()
++ print(file=f)
++ print(i)
++ print(i, file=f)
+
+
+- print >>f
+- print >>f, "Ren'Py Version:", renpy.version
++ print(file=f)
++ print("Ren'Py Version:", renpy.version, file=f)
+
+ f.close()
+
+diff --git a/renpy/persistent.py b/renpy/persistent.py
+index 2201010..8e81dd2 100644
+--- a/renpy/persistent.py
++++ b/renpy/persistent.py
+@@ -26,7 +26,7 @@ import time
+ import renpy
+
+ from renpy.loadsave import dump, loads
+-from cPickle import dumps
++from pickle import dumps
+
+ # The class that's used to hold the persistent data.
+ class Persistent(object):
+@@ -178,7 +178,7 @@ def load(filename):
+
+ # Unserialize the persistent data.
+ try:
+- f = file(filename, "rb")
++ f = open(filename, "rb")
+ s = f.read().decode("zlib")
+ f.close()
+ persistent = loads(s)
+@@ -207,7 +207,7 @@ def init():
+ # Create the backup of the persistent data.
+ v = vars(persistent)
+
+- for k, v in vars(persistent).iteritems():
++ for k, v in vars(persistent).items():
+ backup[k] = safe_deepcopy(v)
+
+ return persistent
+@@ -401,7 +401,7 @@ class _MultiPersistent(object):
+ def save(self):
+
+ fn = self._filename
+- f = file(fn + ".new", "wb")
++ f = open(fn + ".new", "wb")
+ dump(self, f)
+ f.close()
+
+@@ -451,7 +451,7 @@ def MultiPersistent(name):
+ break
+
+ try:
+- rv = loads(file(fn).read())
++ rv = loads(open(fn).read())
+ except:
+ rv = _MultiPersistent()
+
+diff --git a/renpy/pyanalysis.py b/renpy/pyanalysis.py
+index f5267bb..81f17a7 100644
+--- a/renpy/pyanalysis.py
++++ b/renpy/pyanalysis.py
+@@ -19,11 +19,6 @@
+ # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+ # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
+-from __future__ import print_function
+-from __future__ import unicode_literals
+-from __future__ import division
+-from __future__ import absolute_import
+-
+ import renpy # @UnusedImport
+ from renpy.python import py_compile
+
+@@ -31,7 +26,7 @@ from renpy.python import py_compile
+ import ast
+
+ import zlib
+-from cPickle import loads, dumps
++from pickle import loads, dumps
+
+ # The set of names that should be treated as constants.
+ always_constants = { 'True', 'False', 'None' }
+@@ -137,7 +132,7 @@ def pure(fn):
+
+ name = fn
+
+- if not isinstance(name, basestring):
++ if not isinstance(name, str):
+ name = fn.__name__
+
+ if name not in not_constants:
+@@ -297,7 +292,7 @@ class Analysis(object):
+ not changed since the last time we called this function.
+ """
+
+- for i in self.children.values():
++ for i in list(self.children.values()):
+ if not i.at_fixed_point():
+ return False
+
+diff --git a/renpy/python.py b/renpy/python.py
+index 22e7405..c408ad4 100644
+--- a/renpy/python.py
++++ b/renpy/python.py
+@@ -30,7 +30,6 @@ import marshal
+ import random
+ import weakref
+ import re
+-import sets
+ import sys
+
+ import renpy.audio
+@@ -121,7 +120,7 @@ class StoreDict(dict):
+ if k not in self.old:
+ rv[k] = deleted
+
+- for k, v in self.old.iteritems():
++ for k, v in self.old.items():
+
+ new_v = self.get(k, deleted)
+
+@@ -168,7 +167,7 @@ def create_store(name):
+ # Set up the default contents of the store.
+ eval("1", d)
+
+- for k, v in renpy.minstore.__dict__.iteritems():
++ for k, v in renpy.minstore.__dict__.items():
+ if k not in d:
+ d[k] = v
+
+@@ -200,14 +199,14 @@ class StoreBackup():
+ self.ever_been_changed = { }
+
+
+- for k, v in store_dicts.iteritems():
++ for k, v in store_dicts.items():
+ self.store[k] = dict(v)
+ self.old[k] = dict(v.old)
+ self.ever_been_changed[k] = set(v.ever_been_changed)
+
+ def restore(self):
+
+- for k, sd in store_dicts.iteritems():
++ for k, sd in store_dicts.items():
+
+ sd.clear()
+ sd.update(self.store[k])
+@@ -229,7 +228,7 @@ def make_clean_stores():
+
+ global clean_store_backup
+
+- for _k, v in store_dicts.iteritems():
++ for _k, v in store_dicts.items():
+
+ v.old.clear()
+ v.ever_been_changed.clear()
+@@ -294,14 +293,14 @@ def reached(obj, reachable, wait):
+
+ try:
+ # Treat as fields, indexed by strings.
+- for v in vars(obj).itervalues():
++ for v in vars(obj).values():
+ reached(v, reachable, wait)
+ except:
+ pass
+
+ try:
+ # Treat as iterable
+- if not isinstance(obj, basestring):
++ if not isinstance(obj, str):
+ for v in obj.__iter__():
+ reached(v, reachable, wait)
+ except:
+@@ -309,7 +308,7 @@ def reached(obj, reachable, wait):
+
+ try:
+ # Treat as dict.
+- for v in obj.itervalues():
++ for v in obj.values():
+ reached(v, reachable, wait)
+ except:
+ pass
+@@ -326,14 +325,14 @@ def reached_vars(store, reachable, wait):
+ the path by which the object was reached.
+ """
+
+- for v in store.itervalues():
++ for v in store.values():
+ reached(v, reachable, wait)
+
+ for c in renpy.game.contexts:
+ reached(c.info, reachable, wait)
+ reached(c.music, reachable, wait)
+ for d in c.dynamic_stack:
+- for v in d.itervalues():
++ for v in d.values():
+ reached(v, reachable, wait)
+
+
+@@ -417,7 +416,7 @@ def set_filename(filename, offset, tree):
+ worklist.extend(node.getChildNodes())
+
+
+-unicode_re = re.compile(ur'[\u0080-\uffff]')
++unicode_re = re.compile(r'[\u0080-\uffff]')
+
+ def unicode_sub(m):
+ """
+@@ -483,7 +482,7 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False):
+ filename = source.filename
+ lineno = source.linenumber
+
+- source = unicode(source)
++ source = str(source)
+ source = source.replace("\r", "")
+ source = escape_unicode(source)
+
+@@ -504,7 +503,7 @@ def py_compile(source, mode, filename='<none>', lineno=1, ast_node=False):
+
+ return compile(tree, filename, mode)
+
+- except SyntaxError, e:
++ except SyntaxError as e:
+
+ if e.lineno is not None:
+ e.lineno += line_offset
+@@ -590,7 +589,7 @@ class RevertableList(list):
+ self[:] = old
+
+ def revertable_range(*args):
+- return RevertableList(range(*args))
++ return RevertableList(list(range(*args)))
+
+ def revertable_sorted(*args, **kwargs):
+ return RevertableList(sorted(*args, **kwargs))
+@@ -630,7 +629,7 @@ class RevertableDict(dict):
+ return rv
+
+ def get_rollback(self):
+- return self.items()
++ return list(self.items())
+
+ def rollback(self, old):
+ self.clear()
+@@ -869,15 +868,15 @@ class Rollback(renpy.object.Object):
+
+ # Add objects reachable from the stores. (Objects that might be
+ # unreachable at the moment.)
+- for changes in self.stores.itervalues():
+- for _k, v in changes.iteritems():
++ for changes in self.stores.values():
++ for _k, v in changes.items():
+ if v is not deleted:
+ reached(v, reachable, wait)
+
+ # Add in objects reachable through the context.
+ reached(self.context.info, reachable, wait)
+ for d in self.context.dynamic_stack:
+- for v in d.itervalues():
++ for v in d.values():
+ reached(v, reachable, wait)
+
+ # Add in objects reachable through displayables.
+@@ -892,7 +891,7 @@ class Rollback(renpy.object.Object):
+ reached(rb, reachable, wait)
+ else:
+ if renpy.config.debug:
+- print "Removing unreachable:", o
++ print("Removing unreachable:", o)
+
+ pass
+
+@@ -911,12 +910,12 @@ class Rollback(renpy.object.Object):
+ if roll is not None:
+ obj.rollback(roll)
+
+- for name, changes in self.stores.iteritems():
++ for name, changes in self.stores.items():
+ store = store_dicts.get(name, None)
+ if store is None:
+ return
+
+- for name, value in changes.iteritems():
++ for name, value in changes.items():
+ if value is deleted:
+ if name in store:
+ del store[name]
+@@ -1045,7 +1044,7 @@ class RollbackLog(renpy.object.Object):
+ self.rolled_forward = False
+
+ # Reset the point that changes are relative to.
+- for sd in store_dicts.itervalues():
++ for sd in store_dicts.values():
+ sd.begin()
+
+ def complete(self):
+@@ -1059,18 +1058,18 @@ class RollbackLog(renpy.object.Object):
+
+ # Update self.current.stores with the changes from each store.
+ # Also updates .ever_been_changed.
+- for name, sd in store_dicts.iteritems():
++ for name, sd in store_dicts.items():
+ self.current.stores[name] = sd.get_changes()
+
+ # Update the list of mutated objects and what we need to do to
+ # restore them.
+
+- for _i in xrange(4):
++ for _i in range(4):
+
+ self.current.objects = [ ]
+
+ try:
+- for _k, v in self.mutated.iteritems():
++ for _k, v in self.mutated.items():
+
+ if v is None:
+ continue
+@@ -1102,7 +1101,7 @@ class RollbackLog(renpy.object.Object):
+
+ rv = { }
+
+- for store_name, sd in store_dicts.iteritems():
++ for store_name, sd in store_dicts.items():
+ for name in sd.ever_been_changed:
+ if name in sd:
+ rv[store_name + "." + name] = sd[name]
+@@ -1300,7 +1299,7 @@ class RollbackLog(renpy.object.Object):
+
+ # Otherwise, just give up.
+
+- print "Can't find a place to rollback to. Not rolling back."
++ print("Can't find a place to rollback to. Not rolling back.")
+
+ revlog.reverse()
+ self.log = self.log + revlog
+@@ -1422,7 +1421,7 @@ class RollbackLog(renpy.object.Object):
+ clean_stores()
+ renpy.translation.init_translation()
+
+- for name, value in roots.iteritems():
++ for name, value in roots.items():
+
+ if "." in name:
+ store_name, name = name.rsplit(".", 1)
+@@ -1458,7 +1457,7 @@ def py_exec_bytecode(bytecode, hide=False, globals=None, locals=None, store="sto
+ if locals is None:
+ locals = globals #@ReservedAssignment
+
+- exec bytecode in globals, locals
++ exec(bytecode, globals, locals)
+
+
+ def py_exec(source, hide=False, store=None):
+@@ -1471,7 +1470,7 @@ def py_exec(source, hide=False, store=None):
+ else:
+ locals = store #@ReservedAssignment
+
+- exec py_compile(source, 'exec') in store, locals
++ exec(py_compile(source, 'exec'), store, locals)
+
+
+ def py_eval_bytecode(bytecode, globals=None, locals=None): #@ReservedAssignment
+@@ -1485,7 +1484,7 @@ def py_eval_bytecode(bytecode, globals=None, locals=None): #@ReservedAssignment
+ return eval(bytecode, globals, locals)
+
+ def py_eval(code, globals=None, locals=None): #@ReservedAssignment
+- if isinstance(code, basestring):
++ if isinstance(code, str):
+ code = py_compile(code, 'eval')
+ return py_eval_bytecode(code, globals, locals)
+
+@@ -1505,7 +1504,7 @@ def raise_at_location(e, loc):
+ code = compile(node, filename, 'exec')
+
+ # PY3 - need to change to exec().
+- exec code in { "e" : e }
++ exec(code, { "e" : e })
+
+
+ # This was used to proxy accesses to the store. Now it's kept around to deal
+@@ -1524,18 +1523,18 @@ class StoreProxy(object):
+
+ # Code for pickling bound methods.
+ def method_pickle(method):
+- name = method.im_func.__name__
++ name = method.__func__.__name__
+
+- obj = method.im_self
++ obj = method.__self__
+
+ if obj is None:
+- obj = method.im_class
++ obj = method.__self__.__class__
+
+ return method_unpickle, (obj, name)
+
+ def method_unpickle(obj, name):
+ return getattr(obj, name)
+
+-import copy_reg
++import copyreg
+ import types
+-copy_reg.pickle(types.MethodType, method_pickle, method_unpickle)
++copyreg.pickle(types.MethodType, method_pickle, method_unpickle)
+diff --git a/renpy/savelocation.py b/renpy/savelocation.py
+index 9fc813a..567ccc5 100644
+--- a/renpy/savelocation.py
++++ b/renpy/savelocation.py
+@@ -113,7 +113,7 @@ class FileLocation(object):
+
+ self.mtimes = new_mtimes
+
+- for slotname, mtime in new_mtimes.iteritems():
++ for slotname, mtime in new_mtimes.items():
+ if old_mtimes.get(slotname, None) != mtime:
+ clear_slot(slotname)
+
+diff --git a/renpy/screenlang.py b/renpy/screenlang.py
+index 4a537eb..b393a53 100644
+--- a/renpy/screenlang.py
++++ b/renpy/screenlang.py
+@@ -231,12 +231,12 @@ class Parser(object):
+ and expr instances, and adjusts the line number.
+ """
+
+- if isinstance(expr, unicode):
++ if isinstance(expr, str):
+ expr = renpy.python.escape_unicode(expr)
+
+ try:
+ rv = ast.parse(expr, 'eval').body[0].value
+- except SyntaxError, e:
++ except SyntaxError as e:
+ raise renpy.parser.ParseError(
+ filename,
+ lineno + e[1][1] - 1,
+@@ -254,12 +254,12 @@ class Parser(object):
+ adjusts the line number. Returns a list of statements.
+ """
+
+- if isinstance(code, unicode):
++ if isinstance(code, str):
+ code = renpy.python.escape_unicode(code)
+
+ try:
+ rv = ast.parse(code, 'exec')
+- except SyntaxError, e:
++ except SyntaxError as e:
+
+ raise renpy.parser.ParseError(
+ filename,
+diff --git a/renpy/script.py b/renpy/script.py
+index 61d6b5f..8773787 100644
+--- a/renpy/script.py
++++ b/renpy/script.py
+@@ -33,7 +33,7 @@ import marshal
+ import struct
+ import zlib
+
+-from cPickle import loads, dumps
++from pickle import loads, dumps
+ import shutil
+
+ # The version of the dumped script.
+@@ -103,7 +103,7 @@ class Script(object):
+ renpy.game.script = self
+
+ if os.path.exists(renpy.config.renpy_base + "/lock.txt"):
+- self.key = file(renpy.config.renpy_base + "/lock.txt", "rb").read()
++ self.key = open(renpy.config.renpy_base + "/lock.txt", "rb").read()
+ else:
+ self.key = None
+
+@@ -195,7 +195,7 @@ class Script(object):
+ continue
+
+ try:
+- os.makedirs(os.path.dirname(target_fn), 0700)
++ os.makedirs(os.path.dirname(target_fn), 0o700)
+ except:
+ pass
+
+@@ -402,7 +402,7 @@ class Script(object):
+ name = node.name
+
+ if name in self.namemap:
+- if not isinstance(bad_name, basestring):
++ if not isinstance(bad_name, str):
+ bad_name = name
+ bad_node = node
+ old_node = self.namemap[name]
+@@ -566,7 +566,7 @@ class Script(object):
+ self.assign_names(stmts, fullfn)
+
+ try:
+- f = file(rpycfn, "wb")
++ f = open(rpycfn, "wb")
+
+ self.write_rpyc_header(f)
+ self.write_rpyc_data(f, 1, dumps((data, stmts), 2))
+@@ -615,7 +615,7 @@ class Script(object):
+ return None, None
+
+ if data is None:
+- print "Failed to load", fn
++ print("Failed to load", fn)
+ return None, None
+
+ if not isinstance(data, dict):
+@@ -698,11 +698,11 @@ class Script(object):
+ data, stmts = self.load_file(dir, fn + compiled)
+
+ if data is None:
+- print "Could not load " + rpycfn
++ print("Could not load " + rpycfn)
+
+ except:
+ if "RENPY_RPYC_EXCEPTIONS" in os.environ:
+- print "While loading", rpycfn
++ print("While loading", rpycfn)
+ raise
+
+ pass
+@@ -781,7 +781,7 @@ class Script(object):
+ elif i.mode == 'eval':
+ code = renpy.python.py_compile_eval_bytecode(i.source, filename=i.location[0], lineno=i.location[1])
+
+- except SyntaxError, e:
++ except SyntaxError as e:
+
+ text = e.text
+
+diff --git a/renpy/scriptedit.py b/renpy/scriptedit.py
+index 92994d0..6818c59 100644
+--- a/renpy/scriptedit.py
++++ b/renpy/scriptedit.py
+@@ -90,7 +90,7 @@ def adjust_line_locations(filename, linenumber, char_offset, line_offset):
+
+ new_lines = { }
+
+- for key, line in lines.iteritems():
++ for key, line in lines.items():
+
+ (fn, ln) = key
+
+@@ -204,13 +204,13 @@ def first_and_last_nodes(nodes):
+
+ for i in nodes:
+ for j in nodes:
+- if j.next is i:
++ if j.__next__ is i:
+ break
+ else:
+ firsts.append(i)
+
+ for j in nodes:
+- if i.next is j:
++ if i.__next__ is j:
+ break
+
+ else:
+@@ -288,7 +288,7 @@ def remove_from_ast(filename, linenumber):
+ if i in nodes:
+ continue
+
+- i.replace_next(first, last.next)
++ i.replace_next(first, last.__next__)
+
+ new_stmts.append(i)
+
+diff --git a/renpy/sl2/slast.py b/renpy/sl2/slast.py
+index defd86f..37022db 100644
+--- a/renpy/sl2/slast.py
++++ b/renpy/sl2/slast.py
+@@ -30,7 +30,7 @@
+ import ast
+ import collections
+ import linecache
+-from cPickle import loads, dumps
++from pickle import loads, dumps
+ import zlib
+
+ import renpy.display
+@@ -1398,7 +1398,7 @@ class SLFor(SLBlock):
+ if c is None:
+ return
+
+- for child_cache in c.values():
++ for child_cache in list(c.values()):
+ for i in self.children:
+ i.copy_on_change(child_cache)
+
+@@ -1422,7 +1422,7 @@ class SLPython(SLNode):
+ analysis.python(self.code.source)
+
+ def execute(self, context):
+- exec self.code.bytecode in context.globals, context.scope
++ exec(self.code.bytecode, context.globals, context.scope)
+
+ def prepare(self, analysis):
+ self.constant = NOT_CONST
+diff --git a/renpy/styledata/styleutil.py b/renpy/styledata/styleutil.py
+index fcd65c1..054f7c8 100644
+--- a/renpy/styledata/styleutil.py
++++ b/renpy/styledata/styleutil.py
+@@ -22,6 +22,7 @@
+ # Utility functions used by the various property functions:
+
+ import renpy
++import collections
+
+ def none_is_null(o):
+ if o is None:
+@@ -36,7 +37,7 @@ def expand_focus_mask(v):
+ return v
+ elif v is True:
+ return v
+- elif callable(v):
++ elif isinstance(v, collections.Callable):
+ return v
+ else:
+ return renpy.easy.displayable(v)
+diff --git a/renpy/text/extras.py b/renpy/text/extras.py
+index 2fa4d49..75e2f3f 100644
+--- a/renpy/text/extras.py
++++ b/renpy/text/extras.py
+@@ -73,7 +73,7 @@ def check_text_tags(s):
+ else:
+ all_tags = text_tags
+
+- tokens = textsupport.tokenize(unicode(s))
++ tokens = textsupport.tokenize(str(s))
+
+ tag_stack = [ ]
+
+diff --git a/renpy/text/font.py b/renpy/text/font.py
+index 07ac3ab..12e4d0f 100644
+--- a/renpy/text/font.py
++++ b/renpy/text/font.py
+@@ -92,7 +92,7 @@ class ImageFont(object):
+ return
+
+ for g in glyphs:
+- c = unichr(g.character)
++ c = chr(g.character)
+
+ cxo, cyo = self.offsets[c]
+ x = g.x + xo + cxo
+@@ -143,20 +143,20 @@ class SFont(ImageFont):
+ self.baseline = height # W0201
+
+ # Create space characters.
+- self.chars[u' '] = renpy.display.pgrender.surface((self.spacewidth, height), True)
+- self.width[u' '] = self.spacewidth
+- self.advance[u' '] = self.spacewidth
+- self.offsets[u' '] = (0, 0)
++ self.chars[' '] = renpy.display.pgrender.surface((self.spacewidth, height), True)
++ self.width[' '] = self.spacewidth
++ self.advance[' '] = self.spacewidth
++ self.offsets[' '] = (0, 0)
+
+- self.chars[u'\u200b'] = renpy.display.pgrender.surface((0, height), True)
+- self.width[u'\u200b'] = 0
+- self.advance[u'\u200b'] = 0
+- self.offsets[u'\u200b'] = (0, 0)
++ self.chars['\u200b'] = renpy.display.pgrender.surface((0, height), True)
++ self.width['\u200b'] = 0
++ self.advance['\u200b'] = 0
++ self.offsets['\u200b'] = (0, 0)
+
+- self.chars[u'\u00a0'] = self.chars[u' ']
+- self.width[u'\u00a0'] = self.width[u' ']
+- self.advance[u'\u00a0'] = self.advance[u' ']
+- self.offsets[u'\u00a0'] = self.offsets[u' ']
++ self.chars['\u00a0'] = self.chars[' ']
++ self.width['\u00a0'] = self.width[' ']
++ self.advance['\u00a0'] = self.advance[' ']
++ self.offsets['\u00a0'] = self.offsets[' ']
+
+ # The color key used to separate characters.
+ i = 0
+@@ -234,7 +234,7 @@ class MudgeFont(ImageFont):
+ if char < 0:
+ continue
+
+- c = unichr(char)
++ c = chr(char)
+ x = int(e.attrib["x"])
+ y = int(e.attrib["y"])
+ w = int(e.attrib["width"])
+@@ -254,22 +254,22 @@ class MudgeFont(ImageFont):
+ self.baseline = height # W0201
+
+ # Create space characters.
+- if u' ' not in self.chars:
+- self.chars[u' '] = renpy.display.pgrender.surface((self.spacewidth, height), True)
+- self.width[u' '] = self.spacewidth
+- self.advance[u' '] = self.spacewidth
+- self.offsets[u' '] = (0, 0)
++ if ' ' not in self.chars:
++ self.chars[' '] = renpy.display.pgrender.surface((self.spacewidth, height), True)
++ self.width[' '] = self.spacewidth
++ self.advance[' '] = self.spacewidth
++ self.offsets[' '] = (0, 0)
+
+- if u'\u00a0' not in self.chars:
+- self.chars[u'\u00a0'] = self.chars[u' ']
+- self.width[u'\u00a0'] = self.width[u' ']
+- self.advance[u'\u00a0'] = self.advance[u' ']
+- self.offsets[u'\u00a0'] = self.offsets[u' ']
++ if '\u00a0' not in self.chars:
++ self.chars['\u00a0'] = self.chars[' ']
++ self.width['\u00a0'] = self.width[' ']
++ self.advance['\u00a0'] = self.advance[' ']
++ self.offsets['\u00a0'] = self.offsets[' ']
+
+- self.chars[u'\u200b'] = renpy.display.pgrender.surface((0, height), True)
+- self.width[u'\u200b'] = 0
+- self.advance[u'\u200b'] = 0
+- self.offsets[u'\u200b'] = (0, 0)
++ self.chars['\u200b'] = renpy.display.pgrender.surface((0, height), True)
++ self.width['\u200b'] = 0
++ self.advance['\u200b'] = 0
++ self.offsets['\u200b'] = (0, 0)
+
+
+
+@@ -330,7 +330,7 @@ class BMFont(ImageFont):
+ elif kind == "page":
+ pages[int(args["id"])] = renpy.display.im.Image(args["file"]).load(unscaled=True)
+ elif kind == "char":
+- c = unichr(int(args["id"]))
++ c = chr(int(args["id"]))
+ x = int(args["x"])
+ y = int(args["y"])
+ w = int(args["width"])
+@@ -350,17 +350,17 @@ class BMFont(ImageFont):
+
+ f.close()
+
+- if u'\u00a0' not in self.chars:
+- self.chars[u'\u00a0'] = self.chars[u' ']
+- self.width[u'\u00a0'] = self.width[u' ']
+- self.advance[u'\u00a0'] = self.advance[u' ']
+- self.offsets[u'\u00a0'] = self.offsets[u' ']
++ if '\u00a0' not in self.chars:
++ self.chars['\u00a0'] = self.chars[' ']
++ self.width['\u00a0'] = self.width[' ']
++ self.advance['\u00a0'] = self.advance[' ']
++ self.offsets['\u00a0'] = self.offsets[' ']
+
+
+- self.chars[u'\u200b'] = renpy.display.pgrender.surface((0, self.height), True)
+- self.width[u'\u200b'] = 0
+- self.advance[u'\u200b'] = 0
+- self.offsets[u'\u200b'] = (0, 0)
++ self.chars['\u200b'] = renpy.display.pgrender.surface((0, self.height), True)
++ self.width['\u200b'] = 0
++ self.advance['\u200b'] = 0
++ self.offsets['\u200b'] = (0, 0)
+
+ class ScaledImageFont(ImageFont):
+ """
+@@ -376,14 +376,14 @@ class ScaledImageFont(ImageFont):
+ self.baseline = scale(parent.baseline)
+ self.default_kern = scale(parent.default_kern)
+
+- self.width = { k : scale(v) for k, v in parent.width.iteritems() }
+- self.advance = { k : scale(v) for k, v in parent.advance.iteritems() }
+- self.offsets = { k : (scale(v[0]), scale(v[1])) for k, v in parent.offsets.iteritems() }
+- self.kerns = { k : scale(v) for k, v in parent.kerns.iteritems() }
++ self.width = { k : scale(v) for k, v in parent.width.items() }
++ self.advance = { k : scale(v) for k, v in parent.advance.items() }
++ self.offsets = { k : (scale(v[0]), scale(v[1])) for k, v in parent.offsets.items() }
++ self.kerns = { k : scale(v) for k, v in parent.kerns.items() }
+
+ self.chars = { }
+
+- for k, v in parent.chars.iteritems():
++ for k, v in parent.chars.items():
+ w, h = v.get_size()
+ nw = scale(w)
+ nh = scale(h)
+@@ -392,7 +392,7 @@ class ScaledImageFont(ImageFont):
+
+ def register_sfont(name=None, size=None, bold=False, italics=False, underline=False,
+ filename=None, spacewidth=10, default_kern=0, kerns={},
+- charset=u"!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"):
++ charset="!\"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\\]^_`abcdefghijklmnopqrstuvwxyz{|}~"):
+
+ """
+ :doc: image_fonts
+@@ -570,12 +570,12 @@ def load_face(fn):
+
+ pygame.sysfont.initsysfonts()
+
+- for v in pygame.sysfont.Sysfonts.itervalues():
++ for v in pygame.sysfont.Sysfonts.values():
+ if v is not None:
+- for _flags, ffn in v.iteritems():
++ for _flags, ffn in v.items():
+ for i in fonts:
+ if ffn.lower().endswith(i):
+- font_file = file(ffn, "rb")
++ font_file = open(ffn, "rb")
+ break
+
+ if font_file:
+@@ -661,7 +661,7 @@ def free_memory():
+
+
+ def load_image_fonts():
+- for i in image_fonts.itervalues():
++ for i in image_fonts.values():
+ i.load()
+
+
+diff --git a/renpy/text/text.py b/renpy/text/text.py
+index 0d78ddf..748dbbd 100644
+--- a/renpy/text/text.py
++++ b/renpy/text/text.py
+@@ -844,7 +844,7 @@ class Layout(object):
+ if isinstance(i[0], (TextSegment, SpaceSegment, DisplayableSegment)):
+ return
+
+- line.extend(tss[-1].subsegment(u" "))
++ line.extend(tss[-1].subsegment(" "))
+
+ for type, text in tokens: #@ReservedAssignment
+
+@@ -864,7 +864,7 @@ class Layout(object):
+ continue
+
+ elif type == DISPLAYABLE:
+- line.append((DisplayableSegment(tss[-1], text, renders), u""))
++ line.append((DisplayableSegment(tss[-1], text, renders), ""))
+ continue
+
+ # Otherwise, we have a text tag.
+@@ -896,7 +896,7 @@ class Layout(object):
+
+ elif tag == "space":
+ width = self.scale_int(int(value))
+- line.append((SpaceSegment(tss[-1], width=width), u""))
++ line.append((SpaceSegment(tss[-1], width=width), ""))
+
+ elif tag == "vspace":
+ # Duplicates from the newline tag.
+@@ -906,7 +906,7 @@ class Layout(object):
+ if line:
+ paragraphs.append(line)
+
+- line = [ (SpaceSegment(tss[-1], height=height), u"") ]
++ line = [ (SpaceSegment(tss[-1], height=height), "") ]
+ paragraphs.append(line)
+
+ line = [ ]
+@@ -1295,7 +1295,7 @@ class Text(renpy.display.core.Displayable):
+
+ # Check that the text is all text-able things.
+ for i in text:
+- if not isinstance(i, (basestring, renpy.display.core.Displayable)):
++ if not isinstance(i, (str, renpy.display.core.Displayable)):
+ if renpy.config.developer:
+ raise Exception("Cannot display {0!r} as text.".format(i))
+ else:
+@@ -1345,15 +1345,15 @@ class Text(renpy.display.core.Displayable):
+ s = ""
+
+ for i in self.text:
+- if isinstance(i, basestring):
++ if isinstance(i, str):
+ s += i
+
+ if len(s) > 25:
+- s = s[:24] + u"\u2026"
++ s = s[:24] + "\u2026"
+ break
+
+ s = s.replace("\\", "\\\\").replace("\"", "\\\"").replace("\n", "\\n")
+- return u"Text \"{}\"".format(s)
++ return "Text \"{}\"".format(s)
+
+ def _scope(self, scope, update=True):
+ """
+@@ -1377,12 +1377,12 @@ class Text(renpy.display.core.Displayable):
+
+ # Perform substitution as necessary.
+ for i in text:
+- if isinstance(i, basestring):
++ if isinstance(i, str):
+ if substitute is not False:
+ i, did_sub = renpy.substitutions.substitute(i, scope, substitute)
+ uses_scope = uses_scope or did_sub
+
+- i = unicode(i)
++ i = str(i)
+
+ new_text.append(i)
+
+@@ -1482,7 +1482,7 @@ class Text(renpy.display.core.Displayable):
+
+ for i in self.text:
+
+- if not isinstance(i, basestring):
++ if not isinstance(i, str):
+ continue
+
+ rv.append(i)
+@@ -1826,11 +1826,11 @@ class Text(renpy.display.core.Displayable):
+
+ for i in text:
+
+- if isinstance(i, unicode):
++ if isinstance(i, str):
+ tokens.extend(textsupport.tokenize(i))
+
+ elif isinstance(i, str):
+- tokens.extend(textsupport.tokenize(unicode(i)))
++ tokens.extend(textsupport.tokenize(str(i)))
+
+ elif isinstance(i, renpy.display.core.Displayable):
+ tokens.append((DISPLAYABLE, i))
+@@ -1854,7 +1854,7 @@ class Text(renpy.display.core.Displayable):
+ kind, text = t
+
+ if kind == TEXT and renpy.config.replace_text:
+- rv.append((TEXT, unicode(renpy.config.replace_text(text))))
++ rv.append((TEXT, str(renpy.config.replace_text(text))))
+
+ elif kind != TAG:
+ rv.append(t)
+@@ -1906,7 +1906,7 @@ class Text(renpy.display.core.Displayable):
+
+ for kind2, text2 in new_contents:
+ if isinstance(text2, str):
+- text2 = unicode(text2)
++ text2 = str(text2)
+
+ new_tokens.append((kind2, text2))
+
+diff --git a/renpy/translation.py b/renpy/translation.py
+index 3b62fee..0cd1150 100644
+--- a/renpy/translation.py
++++ b/renpy/translation.py
+@@ -101,7 +101,7 @@ class ScriptTranslator(object):
+ continue
+
+ if n.name.__class__ is not tuple:
+- if isinstance(n.name, basestring):
++ if isinstance(n.name, str):
+ label = n.name
+
+ type_n = n.__class__
+@@ -360,16 +360,16 @@ class StringTranslator(object):
+
+ f = open_tl_file(fn)
+
+- f.write(u"translate {} strings:\n".format(language))
+- f.write(u"\n")
++ f.write("translate {} strings:\n".format(language))
++ f.write("\n")
+
+ for i in self.unknown:
+
+ i = quote_unicode(i)
+
+- f.write(u" old \"{}\"\n".format(i))
+- f.write(u" new \"{}\"\n".format(i))
+- f.write(u"\n")
++ f.write(" old \"{}\"\n".format(i))
++ f.write(" new \"{}\"\n".format(i))
++ f.write("\n")
+
+ f.close()
+
+@@ -597,13 +597,13 @@ def open_tl_file(fn):
+ pass
+
+ f = io.open(fn, "a", encoding="utf-8")
+- f.write(u"\ufeff")
++ f.write("\ufeff")
+
+ else:
+ f = io.open(fn, "a", encoding="utf-8")
+
+- f.write(u"# TODO: Translation updated at {}\n".format(time.strftime("%Y-%m-%d %H:%M")))
+- f.write(u"\n")
++ f.write("# TODO: Translation updated at {}\n".format(time.strftime("%Y-%m-%d %H:%M")))
++ f.write("\n")
+
+ return f
+
+@@ -682,17 +682,17 @@ class TranslateFile(object):
+ if label is None:
+ label = ""
+
+- self.f.write(u"# {}:{}\n".format(t.filename, t.linenumber))
+- self.f.write(u"translate {} {}:\n".format(self.language, t.identifier))
+- self.f.write(u"\n")
++ self.f.write("# {}:{}\n".format(t.filename, t.linenumber))
++ self.f.write("translate {} {}:\n".format(self.language, t.identifier))
++ self.f.write("\n")
+
+ for n in t.block:
+- self.f.write(u" # " + n.get_code() + "\n")
++ self.f.write(" # " + n.get_code() + "\n")
+
+ for n in t.block:
+- self.f.write(u" " + n.get_code(self.filter) + "\n")
++ self.f.write(" " + n.get_code(self.filter) + "\n")
+
+- self.f.write(u"\n")
++ self.f.write("\n")
+
+ def write_strings(self):
+ """
+@@ -715,15 +715,15 @@ class TranslateFile(object):
+ started = True
+
+ self.open()
+- self.f.write(u"translate {} strings:\n".format(self.language))
+- self.f.write(u"\n")
++ self.f.write("translate {} strings:\n".format(self.language))
++ self.f.write("\n")
+
+ fs = self.filter(s)
+
+- self.f.write(u" # {}:{}\n".format(filename, line))
+- self.f.write(u" old \"{}\"\n".format(quote_unicode(s)))
+- self.f.write(u" new \"{}\"\n".format(quote_unicode(fs)))
+- self.f.write(u"\n")
++ self.f.write(" # {}:{}\n".format(filename, line))
++ self.f.write(" old \"{}\"\n".format(quote_unicode(s)))
++ self.f.write(" new \"{}\"\n".format(quote_unicode(fs)))
++ self.f.write("\n")
+
+ def null_filter(s):
+ return s
+diff --git a/renpy/ui.py b/renpy/ui.py
+index 464440b..26ee95d 100644
+--- a/renpy/ui.py
++++ b/renpy/ui.py
+@@ -477,7 +477,7 @@ class Wrapper(renpy.object.Object):
+
+ try:
+ w = self.function(*args, **keyword)
+- except TypeError, e:
++ except TypeError as e:
+ etype, e, tb = sys.exc_info(); etype
+
+ if tb.tb_next is None:
+@@ -799,9 +799,9 @@ def menu(menuitems,
+ text = choice_chosen_style
+ button = choice_chosen_button_style
+
+- if isinstance(button, basestring):
++ if isinstance(button, str):
+ button = getattr(renpy.game.style, button)
+- if isinstance(text, basestring):
++ if isinstance(text, str):
+ text = getattr(renpy.game.style, text)
+
+ button = button[label]
+@@ -826,7 +826,7 @@ def imagemap_compat(ground,
+ button_style='hotspot',
+ **properties):
+
+- if isinstance(button_style, basestring):
++ if isinstance(button_style, str):
+ button_style = getattr(renpy.game.style, button_style)
+
+ fixed(style=style, **properties)
+@@ -995,7 +995,7 @@ def _bar(*args, **properties):
+ else:
+ style = value.get_style()[0]
+
+- if isinstance(style, basestring):
++ if isinstance(style, str):
+ style = style_group_style(style, NoStyleGroupGiven)
+
+ properties["style"] = style
+@@ -1038,7 +1038,7 @@ def viewport(scrollbars=None, **properties):
+ viewport_properties = { }
+ side_properties = { }
+
+- for k, v in properties.iteritems():
++ for k, v in properties.items():
+ if k.startswith("side_"):
+ side_properties[k[5:]] = v
+ else:
+@@ -1293,7 +1293,7 @@ returns = renpy.curry.curry(_returns)
+
+ def _jumps(label, transition=None):
+
+- if isinstance(transition, basestring):
++ if isinstance(transition, str):
+ transition = getattr(renpy.config, transition)
+
+ if transition is not None:
+@@ -1343,6 +1343,6 @@ def screen_id(id_, d):
+
+ # Update the wrappers to have names.
+ k, v = None, None
+-for k, v in globals().iteritems():
++for k, v in globals().items():
+ if isinstance(v, Wrapper):
+ v.name = k
+diff --git a/renpy/warp.py b/renpy/warp.py
+index 41bd1be..a81102a 100644
+--- a/renpy/warp.py
++++ b/renpy/warp.py
+@@ -56,7 +56,7 @@ def warp():
+
+ prev = { }
+
+- workset = sets.Set([ n for n in renpy.game.script.namemap.itervalues() if isinstance(n, renpy.ast.Scene) ])
++ workset = sets.Set([ n for n in renpy.game.script.namemap.values() if isinstance(n, renpy.ast.Scene) ])
+ seenset = sets.Set(workset)
+
+ # This is called to indicate that next can be executed following node.
+@@ -100,7 +100,7 @@ def warp():
+ add(n, n.get_next())
+
+ elif getattr(n, 'next', None) is not None:
+- add(n, n.next)
++ add(n, n.__next__)
+
+ # Now, attempt to find a statement preceding the line that the
+ # user wants to warp to.
+diff --git a/renpy.py b/renpy.py
+index 7548cf6..847b8d0 100644
+--- a/renpy.py
++++ b/renpy.py
+@@ -68,7 +68,7 @@ def path_to_saves(gamedir, save_directory=None):
+ if os.path.isdir(rv) and test_writable(rv):
+ break
+
+- print "Saving to", rv
++ print("Saving to", rv)
+
+ # We return the last path as the default.
+
+@@ -94,7 +94,7 @@ def path_to_saves(gamedir, save_directory=None):
+ except:
+ rv = url.path.UTF8String().decode("utf-8")
+
+- print "Saving to", rv
++ print("Saving to", rv)
+ return rv
+
+ # No save directory given.
+@@ -151,7 +151,7 @@ try:
+ import ast; ast
+ except:
+ raise
+- print "Ren'Py requires at least python 2.6."
++ print("Ren'Py requires at least python 2.6.")
+ sys.exit(0)
+
+ android = ("ANDROID_PRIVATE" in os.environ)
+@@ -186,8 +186,8 @@ def main():
+ try:
+ import renpy.bootstrap
+ except ImportError:
+- print >>sys.stderr, "Could not import renpy.bootstrap. Please ensure you decompressed Ren'Py"
+- print >>sys.stderr, "correctly, preserving the directory structure."
++ print("Could not import renpy.bootstrap. Please ensure you decompressed Ren'Py", file=sys.stderr)
++ print("correctly, preserving the directory structure.", file=sys.stderr)
+ raise
+
+ renpy.bootstrap.bootstrap(renpy_base)
+diff --git a/tutorial/game/examples.rpy b/tutorial/game/examples.rpy
+index 6612f99..216ffd2 100644
+--- a/tutorial/game/examples.rpy
++++ b/tutorial/game/examples.rpy
+@@ -128,7 +128,7 @@ init python hide:
+
+ for fn in files:
+
+- f = file(fn, "r")
++ f = open(fn, "r")
+
+ open_examples = set()
+
diff --git a/pcr/renpy-python3/renpy b/pcr/renpy-python3/renpy
new file mode 100644
index 000000000..dfa11b101
--- /dev/null
+++ b/pcr/renpy-python3/renpy
@@ -0,0 +1,3 @@
+#!/usr/bin/sh
+
+exec python /usr/share/renpy/renpy.py "$@"
diff --git a/pcr/renpy-python3/renpy-ffmpeg30.patch b/pcr/renpy-python3/renpy-ffmpeg30.patch
new file mode 100644
index 000000000..026719750
--- /dev/null
+++ b/pcr/renpy-python3/renpy-ffmpeg30.patch
@@ -0,0 +1,94 @@
+From 4aac7ca5a59960ec776e3c4cd74a30f269342502 Mon Sep 17 00:00:00 2001
+From: Markus Koschany <apo@debian.org>
+Date: Wed, 27 Jan 2016 00:43:37 +0100
+Subject: [PATCH] ffmpeg
+
+---
+ module/ffdecode.c | 26 +++++++++++++-------------
+ 1 file changed, 13 insertions(+), 13 deletions(-)
+
+diff --git a/module/ffdecode.c b/module/ffdecode.c
+index 71704cf..085000d 100644
+--- a/module/ffdecode.c
++++ b/module/ffdecode.c
+@@ -103,8 +103,8 @@ typedef struct VideoState {
+ compensation */
+
+ #ifndef HAS_RESAMPLE
+- uint8_t audio_buf1[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2] __attribute__ ((aligned (16))) ;
+- uint8_t audio_buf2[(AVCODEC_MAX_AUDIO_FRAME_SIZE * 3) / 2] __attribute__ ((aligned (16))) ;
++ uint8_t audio_buf1[(192000 * 3) / 2] __attribute__ ((aligned (16))) ;
++ uint8_t audio_buf2[(192000 * 3) / 2] __attribute__ ((aligned (16))) ;
+ #else
+ uint8_t *audio_buf1;
+ #endif
+@@ -583,7 +583,7 @@ static int video_refresh(void *opaque)
+
+ is->first_frame = 0;
+
+- av_free(vp->frame);
++ av_frame_free(&vp->frame);
+ vp->frame = NULL;
+
+ /* update queue size and signal for next picture */
+@@ -635,13 +635,13 @@ static void alloc_picture(void *opaque, PyObject *pysurf)
+
+ pixel = SDL_MapRGBA(surf->format, 1, 2, 3, 4);
+ if (bytes[0] == 4 && bytes[1] == 1) {
+- vp->fmt = PIX_FMT_ARGB;
++ vp->fmt = AV_PIX_FMT_ARGB;
+ } else if (bytes[0] == 4 && bytes[1] == 3) {
+- vp->fmt = PIX_FMT_ABGR;
++ vp->fmt = AV_PIX_FMT_ABGR;
+ } else if (bytes[0] == 1) {
+- vp->fmt = PIX_FMT_RGBA;
++ vp->fmt = AV_PIX_FMT_RGBA;
+ } else {
+- vp->fmt = PIX_FMT_BGRA;
++ vp->fmt = AV_PIX_FMT_BGRA;
+ }
+
+ pixel = SDL_MapRGBA(surf->format, 0, 0, 0, 255);
+@@ -764,7 +764,7 @@ static int video_thread(void *arg)
+ double pts;
+
+ for(;;) {
+- frame = avcodec_alloc_frame();
++ frame = av_frame_alloc();
+
+ while (is->paused && !is->videoq.abort_request) {
+ SDL_Delay(2);
+@@ -824,10 +824,10 @@ static int audio_decode_frame(VideoState *is, double *pts_ptr)
+ int resample_changed, audio_resample;
+
+ if (!is->frame) {
+- if (!(is->frame = avcodec_alloc_frame()))
++ if (!(is->frame = av_frame_alloc()))
+ return AVERROR(ENOMEM);
+ } else
+- avcodec_get_frame_defaults(is->frame);
++ av_frame_unref(is->frame);
+
+ if (flush_complete)
+ break;
+@@ -1244,9 +1244,9 @@ static int stream_component_open(VideoState *is, int stream_index)
+ /* prepare audio output */
+ if (enc->codec_type == AVMEDIA_TYPE_AUDIO) {
+ if (enc->channels > 0) {
+- enc->request_channels = FFMIN(2, enc->channels);
++ enc->request_channel_layout = av_get_default_channel_layout(FFMIN(2, enc->channels));
+ } else {
+- enc->request_channels = 2;
++ enc->request_channel_layout = av_get_default_channel_layout(2);
+ }
+ }
+
+@@ -1653,7 +1653,7 @@ void ffpy_stream_close(VideoState *is)
+ for(i=0; i<VIDEO_PICTURE_QUEUE_SIZE; i++) {
+ vp = &is->pictq[i];
+ if (vp->frame) {
+- av_free(vp->frame);
++ av_frame_free(&vp->frame);
+ }
+ }
+
diff --git a/pcr/renpy-python3/renpy.desktop b/pcr/renpy-python3/renpy.desktop
new file mode 100644
index 000000000..4fa6bbd16
--- /dev/null
+++ b/pcr/renpy-python3/renpy.desktop
@@ -0,0 +1,9 @@
+[Desktop Entry]
+Version=1.0
+Type=Application
+Name=Ren'Py
+GenericName=renpy
+Comment=Ren'Py is a visual novel engine that helps you use words, images, and sounds to tell interactive stories that run on computers and mobile devices.
+Icon=renpy
+Exec=renpy
+Categories=Game;AdventureGame;
diff --git a/pcr/renpy-python3/renpy.png b/pcr/renpy-python3/renpy.png
new file mode 100644
index 000000000..c7e1f9dfb
--- /dev/null
+++ b/pcr/renpy-python3/renpy.png
Binary files differ