# Copyright 2004-2022 Tom Rothamel # # Permission is hereby granted, free of charge, to any person # obtaining a copy of this software and associated documentation files # (the "Software"), to deal in the Software without restriction, # including without limitation the rights to use, copy, modify, merge, # publish, distribute, sublicense, and/or sell copies of the Software, # and to permit persons to whom the Software is furnished to do so, # subject to the following conditions: # # The above copyright notice and this permission notice shall be # included in all copies or substantial portions of the Software. # # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, # EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF # MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND # NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE # LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION # OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION # WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. init python in distribute: import time import zipfile import tarfile import zlib import struct import stat import shutil import sys import threading from renpy import six from zipfile import crc32 # Since the long type doesn't exist on py3, define it here if six.PY3: long = int zlib.Z_DEFAULT_COMPRESSION = 5 if PY2: class ZipFile(zipfile.ZipFile): def write_with_info(self, zinfo, filename): """Put the bytes from filename into the archive under the name arcname.""" if not self.fp: raise RuntimeError( "Attempt to write to ZIP archive that was already closed") st = os.stat(filename) isdir = stat.S_ISDIR(st.st_mode) zinfo.file_size = st.st_size zinfo.flag_bits = 0x00 zinfo.header_offset = self.fp.tell() # Start of header bytes self._writecheck(zinfo) self._didModify = True if isdir: zinfo.file_size = 0 zinfo.compress_size = 0 zinfo.CRC = 0 self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo self.fp.write(zinfo.FileHeader()) return with open(filename, "rb") as fp: # Must overwrite CRC and sizes with correct data later zinfo.CRC = CRC = 0 zinfo.compress_size = compress_size = 0 file_size = 0 zip64 = self._allowZip64 and \ zinfo.file_size * 1.05 > zipfile.ZIP64_LIMIT self.fp.write(zinfo.FileHeader(zip64)) if zinfo.compress_type == zipfile.ZIP_DEFLATED: cmpr = zlib.compressobj(zlib.Z_DEFAULT_COMPRESSION, zlib.DEFLATED, -15) else: cmpr = None while 1: buf = fp.read(1024 * 1024) if not buf: break file_size = file_size + len(buf) CRC = crc32(buf, CRC) & 0xffffffff if cmpr: buf = cmpr.compress(buf) compress_size = compress_size + len(buf) self.fp.write(buf) if cmpr: buf = cmpr.flush() compress_size = compress_size + len(buf) self.fp.write(buf) zinfo.compress_size = compress_size else: zinfo.compress_size = file_size zinfo.CRC = CRC zinfo.file_size = file_size if not zip64 and self._allowZip64: if file_size > zipfile.ZIP64_LIMIT: raise RuntimeError('File size has increased during compressing') if compress_size > zipfile.ZIP64_LIMIT: raise RuntimeError('Compressed size larger than uncompressed size') # Seek backwards and write CRC and file sizes position = self.fp.tell() # Preserve current position in file self.fp.seek(zinfo.header_offset, 0) self.fp.write(zinfo.FileHeader(zip64)) self.fp.seek(position, 0) self.filelist.append(zinfo) self.NameToInfo[zinfo.filename] = zinfo else: class ZipFile(zipfile.ZipFile): def write_with_info(self, zinfo, filename): if zinfo.filename.endswith("/"): data = b'' else: with open(filename, "rb") as f: data = f.read() self.writestr(zinfo, data) class ZipPackage(object): """ A class that creates a zip file. """ def __init__(self, filename): self.zipfile = ZipFile(filename, "w", zipfile.ZIP_DEFLATED, True) def get_date_time(self, path): """ Gets the datetime for a file. If the time doesn't exist or is weird, use the current time instead. """ try: s = os.stat(path) rv = time.gmtime(s.st_mtime)[:6] # Check that the time is sensible. if rv[0] < 2000: rv = None except Exception: rv = None if rv is None: rv = time.gmtime()[:6] return rv def add_file(self, name, path, xbit): if path is None: raise Exception("path for " + name + " must not be None.") zi = zipfile.ZipInfo(name) zi.date_time = self.get_date_time(path) zi.compress_type = zipfile.ZIP_DEFLATED zi.create_system = 3 if xbit: zi.external_attr = long(0o100755) << 16 else: zi.external_attr = long(0o100644) << 16 self.zipfile.write_with_info(zi, path) def add_directory(self, name, path): if path is None: return zi = zipfile.ZipInfo(name + "/") zi.date_time = self.get_date_time(path) zi.compress_type = zipfile.ZIP_STORED zi.create_system = 3 zi.external_attr = (long(0o040755) << 16) | 0x10 self.zipfile.write_with_info(zi, path) def close(self): self.zipfile.close() class TarPackage(object): def __init__(self, filename, mode, notime=False): """ notime If true, times will be forced to the epoch. """ self.tarfile = tarfile.open(filename, mode) self.tarfile.dereference = True self.notime = notime def add_file(self, name, path, xbit): if path is not None: info = self.tarfile.gettarinfo(path, name) else: info = tarfile.TarInfo(name) info.size = 0 info.mtime = int(time.time()) info.type = tarfile.DIRTYPE if xbit: info.mode = 0o755 else: info.mode = 0o644 info.uid = 1000 info.gid = 1000 info.uname = "renpy" info.gname = "renpy" if self.notime: info.mtime = 0 if info.isreg(): with open(path, "rb") as f: self.tarfile.addfile(info, f) else: self.tarfile.addfile(info) def add_directory(self, name, path): self.add_file(name, path, True) def close(self): self.tarfile.close() class UpdatePackage(TarPackage): def __init__(self, filename, basename, destination): self.path = filename self.basename = basename self.destination = destination TarPackage.__init__(self, filename, "w", notime=True) def close(self): TarPackage.close(self) cmd = [ updater.zsync_path("zsyncmake"), "-z", # -u url to gzipped data - not a local filename! "-u", self.basename + ".update.gz", "-o", os.path.join(self.destination, self.basename + ".zsync"), os.path.abspath(self.path), ] subprocess.check_call([ renpy.fsencode(i) for i in cmd ]) # Build the sums file. This is a file with an adler32 hash of each 64k block # of the zsync file. It's used to help us determine how much of the file is # downloaded. with open(self.path, "rb") as src: with open(renpy.fsencode(os.path.join(self.destination, self.basename + ".sums")), "wb") as sums: while True: data = src.read(65536) if not data: break sums.write(struct.pack("