diff options
author | coadde [Márcio Alexandre Silva Delgado] <coadde@parabola.nu> | 2016-09-08 11:25:10 -0300 |
---|---|---|
committer | coadde [Márcio Alexandre Silva Delgado] <coadde@parabola.nu> | 2016-09-19 16:34:26 -0300 |
commit | d15b5f71fae3b17d8145f7cff1a4d88c2cc1db65 (patch) | |
tree | 1cea12ab4205fdf846ada2422fd854dd66a22c86 /pcr | |
parent | 41872fc502c46f0e918150f1893e570c71b4ded7 (diff) |
add renpy-python3 to [pcr]
Diffstat (limited to 'pcr')
-rw-r--r-- | pcr/renpy-python3/PKGBUILD | 82 | ||||
-rw-r--r-- | pcr/renpy-python3/python3.patch | 5100 | ||||
-rw-r--r-- | pcr/renpy-python3/renpy | 3 | ||||
-rw-r--r-- | pcr/renpy-python3/renpy-ffmpeg30.patch | 94 | ||||
-rw-r--r-- | pcr/renpy-python3/renpy.desktop | 9 | ||||
-rw-r--r-- | pcr/renpy-python3/renpy.png | bin | 0 -> 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 Binary files differnew file mode 100644 index 000000000..c7e1f9dfb --- /dev/null +++ b/pcr/renpy-python3/renpy.png |